Microservices with Spring: When and Why
Introduction
A microservice architecture splits one application into several small applications, each with its own code base, its own database and its own deployments, which talk to each other over the network. Spring Boot made such services easy to write, and Spring Cloud adds what a group of them needs to work together. This tutorial starts the module on microservices with the question that should come first: when is the split worth it, and what does it cost? We will compare a monolith with microservices, build two small services that call each other, see with a real run what happens when one of them is down, and map the problems that appear to the Spring Cloud projects that the next tutorials cover. The code runs on Spring Boot 4.1.2 with Java 25.Monolith and microservices
A monolith is one deployable application. Its modules - say catalog, orders and payments of a bookshop - call each other with ordinary method calls, share one database and one transaction, and are built, tested and released together. In a microservice architecture each of those modules is an application of its own: a separate process with a separate database, released on its own schedule, and reachable by the others only through its API - HTTP, gRPC or messages.
The picture shows the two things that change. Calls between modules become network calls, and the one database becomes several databases that no single transaction covers. Almost every advantage and every cost of microservices follows from these two changes.
What you gain and what you pay
What microservices give -- Independent deployment: a team releases its service without waiting for, or retesting, the rest of the system. This is the main benefit, and it is an organisational one - it matters when several teams work on one product.
- Independent scaling: the service under load gets ten instances while the others keep two.
- Fault isolation: a memory leak in the recommendation service does not take the checkout down - if the callers are written for it.
- Freedom of technology: each service picks the database and the framework version that fit it.
- The network: a call can be slow, fail, or succeed without the answer arriving. A method call inside a monolith does none of that.
- No shared transaction: an order that touches the order database and the payment database cannot simply roll back.
- Operations: ten services mean ten pipelines, ten sets of configuration, logs in ten places, and a request that crosses four of them when something goes wrong.
- Changing an API needs coordination between teams, where a monolith needs a refactoring in the IDE.
Two services that call each other
To make the first cost concrete, here are two Spring Boot applications. The catalog service knows the books and their prices -catalog-service: src/main/java/.../catalog/CatalogApp.java
//CatalogApp.java
package com.javabrahman.cloud.intro.catalog;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class CatalogApp {
record Book(String isbn, String title, int price) { }
private final Map<String, Book> books = Map.of(
"978-1", new Book("978-1", "Effective Java", 450),
"978-2", new Book("978-2", "Spring in Action", 399));
@GetMapping("/books/{isbn}")
ResponseEntity<Book> book(@PathVariable String isbn) {
return ResponseEntity.ofNullable(books.get(isbn));
}
public static void main(String[] args) {
SpringApplication.run(CatalogApp.class, args);
}
}
catalog-service: src/main/resources/application.properties
spring.application.name=catalog-service
server.port=8081
order-service: src/main/java/.../order/OrderApp.java
//OrderApp.java
package com.javabrahman.cloud.intro.order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
@SpringBootApplication
@RestController
public class OrderApp {
record Book(String isbn, String title, int price) { }
record OrderRequest(String isbn, int quantity) { }
record Order(String title, int quantity, int total) { }
private final RestClient catalog;
OrderApp(RestClient.Builder builder, @Value("${catalog.url}") String catalogUrl) {
this.catalog = builder.baseUrl(catalogUrl).build();
}
@PostMapping("/orders")
Order order(@RequestBody OrderRequest request) {
// inside a monolith this line would be a method call: catalog.findBook(isbn)
Book book = catalog.get().uri("/books/{isbn}", request.isbn()).retrieve().body(Book.class);
return new Order(book.title(), request.quantity(), book.price() * request.quantity());
}
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
order-service: src/main/resources/application.properties
spring.application.name=order-service
server.port=8080
catalog.url=http://localhost:8081
> curl -i -X POST http://localhost:8080/orders -H "Content-Type: application/json" -d '{"isbn":"978-1","quantity":2}'
HTTP/1.1 200
Content-Length: 51
Content-Type: application/json
{"title":"Effective Java","quantity":2,"total":900}
# (the catalog service is stopped now)
> curl -i -X POST http://localhost:8080/orders -H "Content-Type: application/json" -d '{"isbn":"978-1","quantity":2}'
HTTP/1.1 500
Content-Type: application/json
{"timestamp":"2026-11-10T05:18:48.957Z","status":500,"error":"Internal Server Error","path":"/orders"}
500 Internal Server Error, although nothing is wrong with the order service or with the request. The log of the order service shows the cause -
From the log of the order service2026-11-10T10:48:48.888+05:30 ERROR 31092 --- [order-service] [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8081/books/978-1": null] with root cause
http://localhost:8081 written into a properties file will be wrong the moment the catalog service moves or gets a second instance. One line of code turned into a list of questions -
- Where is the catalog service right now, and which of its instances should be called?
- What should the order service do when the call fails or takes ten seconds?
- Where do twenty services get their configuration from, and how is it changed without rebuilding them?
- How do clients outside reach the services, and where are they authenticated?
- When one request passes through five services, how do you find out where it failed?
- How does an order that needs a payment and a stock reservation stay consistent without a shared transaction?
What Spring Cloud adds
Spring Cloud is a family of projects built on Spring Boot, released together as a release train named by year - this module uses Spring Cloud 2025.1, the train for Spring Boot 4. Each project answers one of the questions above, and the next tutorials take them one by one -- Configuration: Spring Cloud ConfigClick to read tutorial: Centralized Configuration with Spring Cloud Config serves the configuration of all services from one place.
- Finding each other: service discoveryClick to read tutorial: Service Discovery: Eureka vs Kubernetes with Eureka or Kubernetes, and client-side load balancing.
- One front door: an API gatewayClick to read tutorial: API Gateway with Spring Cloud Gateway routes outside requests to the services.
- Surviving failures: circuit breakers, retries and timeoutsClick to read tutorial: Circuit Breakers and Retries with Resilience4j with Resilience4j.
- Seeing what happens: distributed tracingClick to read tutorial: Distributed Tracing with Micrometer and OpenTelemetry follows one request through all services.
- Talking to each other: REST, messaging or gRPCClick to read tutorial: Inter-Service Communication: REST vs Messaging vs gRPC - and when to use which.
- Staying consistent: the saga patternClick to read tutorial: Saga Pattern for Distributed Transactions replaces the transaction that no longer exists.
Two of these - retries and concurrency limits - also exist in Spring Framework 7 itself, as the tutorial on @Retryable and @ConcurrencyLimitClick to read tutorial: Resilience in Spring 7: @Retryable and @ConcurrencyLimit showed; circuit breakers are what a library such as Resilience4j adds.
When to choose microservices
The costs are paid from the first day; the benefits arrive only if the situation asks for them. Microservices fit when -- Several teams work on one system and block each other's releases. Independent deployment is the benefit that justifies the rest.
- Parts of the system have very different load or availability needs - a search that needs fifty instances next to an admin screen that needs one.
- The boundaries are known: the business areas have been stable for a while, and it is clear which data belongs to whom.
- The organisation can operate them: automated deployment, central logging, monitoring and tracing are in place or will be built first.
- One small team builds the whole product. It gets every cost and none of the organisational benefit.
- The domain is new. Boundaries drawn too early are usually wrong, and moving a boundary between services is far harder than moving a class between packages.
- The goal is cleaner code. A badly structured monolith split into services becomes a badly structured distributed system - a "distributed monolith" where every change still touches several services, which must now be deployed together.
- Most operations need one transaction across several modules, and nobody has decided what should happen when half of such an operation fails.
The modular monolith first
The usual advice in 2026 is to start with a modular monolith: one deployable application whose modules have clear boundaries - separate packages, no access to each other's tables, communication through a small API or through events. It keeps method calls and one transaction, and it keeps the option open: a module with a clean boundary can be extracted into a service when a reason from the first list appears, and only that module. Spring supports this style with Spring Modulith, which verifies the module boundaries in a test.Whichever way a system starts, a service should own its data. Two services that read and write the same tables are one application in two processes: they cannot be released independently, which was the point.
Summary
In this tutorial we looked at what a microservice architecture changes: calls between modules become network calls, and one database becomes several. That buys independent deployment, scaling and fault isolation, and costs a network that can fail, the loss of shared transactions, and much more to operate. Two small Spring Boot services showed the first cost - an order failed with a 500 because the catalog service was down - and the questions that follow from it map to the Spring Cloud projects of this module: configuration, discovery, gateway, resilience, tracing, communication and sagas. Microservices pay off for several teams with stable boundaries and working operations; a small team or a new domain is better served by a modular monolith. The next tutorial begins with the first building block: centralized configuration with Spring Cloud ConfigClick to read tutorial: Centralized Configuration with Spring Cloud Config.Spring Cloud and Microservices Tutorials
Centralized Configuration with Spring Cloud ConfigClick to read tutorial: Centralized Configuration with Spring Cloud Config Service Discovery: Eureka vs KubernetesClick to read tutorial: Service Discovery: Eureka vs Kubernetes API Gateway with Spring Cloud GatewayClick to read tutorial: API Gateway with Spring Cloud Gateway Circuit Breakers and Retries with Resilience4jClick to read tutorial: Circuit Breakers and Retries with Resilience4j Distributed Tracing with Micrometer and OpenTelemetryClick to read tutorial: Distributed Tracing with Micrometer and OpenTelemetry Inter-Service Communication: REST vs Messaging vs gRPCClick to read tutorial: Inter-Service Communication: REST vs Messaging vs gRPC Saga Pattern for Distributed TransactionsClick to read tutorial: Saga Pattern for Distributed Transactions
Centralized Configuration with Spring Cloud ConfigClick to read tutorial: Centralized Configuration with Spring Cloud Config Service Discovery: Eureka vs KubernetesClick to read tutorial: Service Discovery: Eureka vs Kubernetes API Gateway with Spring Cloud GatewayClick to read tutorial: API Gateway with Spring Cloud Gateway Circuit Breakers and Retries with Resilience4jClick to read tutorial: Circuit Breakers and Retries with Resilience4j Distributed Tracing with Micrometer and OpenTelemetryClick to read tutorial: Distributed Tracing with Micrometer and OpenTelemetry Inter-Service Communication: REST vs Messaging vs gRPCClick to read tutorial: Inter-Service Communication: REST vs Messaging vs gRPC Saga Pattern for Distributed TransactionsClick to read tutorial: Saga Pattern for Distributed Transactions