Microservices Introduction
Welcome to our Microservices tutorial! Whether you are a beginner or an experienced developer, this guide will help you understand microservices architecture step by step, with simple explanations, diagrams and working Java examples.
You will learn what microservices are, why companies use them, the building blocks of a microservices system, and how to build, connect and run your first services with Spring Boot and Docker. Along the way, you will find links to detailed guides on each topic.
1. What Is Microservices Architecture?
In simple terms, microservices architecture is a way of building software in which an application is made up of a collection of small, independent and loosely coupled services. Each service handles one business capability, runs in its own process, and talks to other services over the network.
Think of it like building with Lego blocks. Instead of carving a complex structure from one large piece of wood (the monolith), you build it by connecting many small, specialized and replaceable blocks. If one block needs to change, you swap that block without rebuilding the whole model.
Monolith Microservices
+----------------------+ +--------+ +--------+ +--------+
| Users | | Users | |Products| | Orders |
| Products | +---+----+ +---+----+ +---+----+
| Orders | | | |
| Payments | [ DB ] [ DB ] [ DB ]
+----------+-----------+
| Each service: own code, own data,
[ One Database ] own deployment
2. Why Microservices? From Monolith to Services
Most applications start as a monolith: one codebase, one build and one database. That is simple and works well at first. As the application and the team grow, problems appear:
- A small change requires rebuilding and redeploying the whole application.
- Many teams work in the same codebase and get in each other’s way.
- To handle more traffic on one feature, you must scale the entire application.
- One bug, such as a memory leak, can crash everything.
Microservices address these problems by splitting the application along business boundaries, so each part can be built, deployed and scaled on its own. Companies such as Netflix, Amazon and Uber popularized this approach to let hundreds of teams work and release independently. For a detailed comparison, see Monolithic vs Microservices Architecture.
3. Key Characteristics of Microservices
| Characteristic | What it means |
|---|---|
| Single responsibility | Each service focuses on doing one business thing well, such as payments, and owns the data and logic for it |
| Independent development | Teams work on different services at the same time without stepping on each other’s toes |
| Independent deployment | You can update one service without redeploying the whole application |
| Technology freedom | Each service can use the programming language that suits it (polyglot programming) and the database that suits it (polyglot persistence) |
| Decentralized data | Each service has its own database, which avoids the tight coupling a shared database creates |
| Communication through APIs | Services talk over the network with lightweight protocols such as HTTP/REST, or through message brokers such as RabbitMQ and Kafka |
| Built for failure | Services can fail, so the system is designed to cope, using patterns such as circuit breakers and retries |
| Automation | Many services are only manageable with automated builds, tests, deployments and monitoring |
4. Building Blocks of a Microservices System
A real microservices system needs more than the services themselves. These supporting pieces appear in almost every architecture:
| Building block | Purpose | Common tools | Learn more |
|---|---|---|---|
| API gateway | Single entry point for clients; routing, security, rate limiting | Spring Cloud Gateway, Kong, AWS API Gateway | API Gateway |
| Service discovery | Lets services find each other by name instead of fixed addresses | Eureka, Consul, Kubernetes DNS | Service Discovery |
| Database per service | Each service owns and controls its data | PostgreSQL, MongoDB, Redis | Database per Service |
| Message broker | Asynchronous communication through events | Apache Kafka, RabbitMQ | Event-Driven Architecture |
| Resilience | Timeouts, retries and circuit breakers for unreliable networks | Resilience4j | Circuit Breaker |
| Observability | Logs, metrics and traces across all services | Micrometer, Prometheus, Grafana, Zipkin | Section 14 |
| Containers and orchestration | Package and run many services consistently | Docker, Kubernetes | Section 9 |
| Central configuration | Manage settings for all services in one place | Spring Cloud Config, Kubernetes ConfigMaps | — |
5. Example: An Online Shop as Microservices
Let us design a simple online shop. Each service owns one business capability and its own data:
| Service | Responsibility | Owns data about |
|---|---|---|
user-service | Registration, login and profiles | Users and addresses |
product-service | Product catalog and prices | Products |
order-service | Creating and tracking orders | Orders and order lines |
payment-service | Charging customers and refunds | Payments |
notification-service | Emails and text messages | Sent notifications |
Web app / Mobile app
|
[ API Gateway ]
|
+-------+-------+-------+--------+
| | | | |
User Product Order Payment Notification
| | | | |
[DB] [DB] [DB] [DB] [DB]
Order --(REST)--> Product (check price and stock)
Order --(event)--> [ Kafka ] --> Payment, Notification
The order service asks the product service for prices synchronously, because it needs the answer right away. After an order is placed, it publishes an event, and the payment and notification services react asynchronously. The next sections build this step by step.
6. Building Your First Microservice with Spring Boot
Spring Boot is the most popular way to build microservices in Java. A microservice is simply a Spring Boot application with a focused job. Let us build the product service.
6.1 Dependencies
Create a project at start.spring.io with these dependencies, or add them to pom.xml:
<dependencies>
<!-- REST endpoints -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Database access -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Health checks and metrics -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- In-memory database for learning -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
6.2 Configuration
server:
port: 8081
spring:
application:
name: product-service
datasource:
url: jdbc:h2:mem:products
management:
endpoints:
web:
exposure:
include: health,info
Each service runs on its own port locally and has its own name, which is used later for service discovery, logs and tracing.
6.3 The entity and repository
package com.example.product;
import java.math.BigDecimal;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
private int stock;
protected Product() {
// required by JPA
}
public Product(String name, BigDecimal price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
public Long getId() { return id; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
public int getStock() { return stock; }
}
package com.example.product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
6.4 The REST controller
package com.example.product;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductRepository repository;
public ProductController(ProductRepository repository) {
this.repository = repository;
}
@GetMapping
public List<Product> findAll() {
return repository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Product> findById(@PathVariable Long id) {
return repository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> create(@RequestBody NewProduct request) {
Product saved = repository.save(
new Product(request.name(), request.price(), request.stock()));
return ResponseEntity
.created(URI.create("/products/" + saved.getId()))
.body(saved);
}
public record NewProduct(String name, BigDecimal price, int stock) {
}
}
6.5 Try it
# Start the service
./mvnw spring-boot:run
# Create a product
curl -X POST http://localhost:8081/products \
-H "Content-Type: application/json" \
-d '{"name": "Wireless Mouse", "price": 24.99, "stock": 100}'
# Read it back
curl http://localhost:8081/products/1
# Check the health endpoint
curl http://localhost:8081/actuator/health
That is a complete, working microservice: it owns its data, exposes a small REST API and reports its health.
7. Calling Another Microservice
The order service needs product prices. It calls the product service over HTTP using Spring’s RestClient. The address comes from configuration, so it can differ between your laptop, Docker and production:
server:
port: 8082
spring:
application:
name: order-service
services:
product:
url: http://localhost:8081
package com.example.order.client;
import java.math.BigDecimal;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class ProductClient {
private final RestClient restClient;
public ProductClient(RestClient.Builder builder,
@Value("${services.product.url}") String productServiceUrl) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Duration.ofSeconds(2));
factory.setReadTimeout(Duration.ofSeconds(3));
this.restClient = builder
.baseUrl(productServiceUrl)
.requestFactory(factory)
.build();
}
public ProductInfo getProduct(Long productId) {
return restClient.get()
.uri("/products/{id}", productId)
.retrieve()
.body(ProductInfo.class);
}
// The order service keeps its own view of a product: only the fields it needs
public record ProductInfo(Long id, String name, BigDecimal price, int stock) {
}
}
Notice two habits worth building from day one: always set timeouts on remote calls, and give each service its own small model of the data it receives, instead of sharing classes between services. Learn more in Synchronous Communication in Microservices.
8. Communicating with Events
Not every interaction needs an immediate answer. After an order is placed, the order service can simply announce it with an event. Other services react in their own time, and the order service does not even need to know who they are:
@Component
public class NotificationListener {
private final EmailService emailService;
public NotificationListener(EmailService emailService) {
this.emailService = emailService;
}
@KafkaListener(topics = "order-placed", groupId = "notification-service")
public void onOrderPlaced(OrderPlacedEvent event) {
emailService.sendOrderConfirmation(event.customerEmail(), event.orderId());
}
}
If the notification service is down for a few minutes, the events wait in Kafka and are processed when it comes back, so a problem in one service does not stop customers from ordering. See Communication in Microservices and Asynchronous Communication for the full picture.
9. Running Microservices with Docker
Microservices are usually packaged as containers, so every service runs the same way on every machine. A simple Dockerfile for the product service, after building the JAR with ./mvnw package:
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/product-service-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose starts several services together. Inside Compose, services reach each other by their service name:
services:
product-service:
build: ./product-service
ports:
- "8081:8081"
order-service:
build: ./order-service
ports:
- "8082:8082"
environment:
# Overrides services.product.url from application.yml
SERVICES_PRODUCT_URL: http://product-service:8081
depends_on:
- product-service
docker compose up --build
In production, an orchestrator such as Kubernetes takes over: it runs several copies of each service, restarts failed ones, balances traffic and scales services up and down automatically.
10. Advantages of Microservices
- Agility and faster development: small, autonomous teams develop, test and deploy their services quickly and independently.
- Targeted scalability: scale only the services that need it, such as the payment service during a sale, which is more cost-effective.
- Resilience: the failure of one service does not have to crash the whole system; the rest can keep working, perhaps with reduced features.
- Technology freedom: teams can choose the best tools for their specific job.
- Easier to understand: a new developer only needs to learn one focused service to start contributing.
- Faster, safer releases: small deployments are less risky and easier to roll back.
11. Challenges and Disadvantages
- Complexity: managing many distributed services is much harder than one application. This is often called distributed system complexity.
- Network latency and failures: network calls are slower than method calls and can fail, so the system must be designed to handle this.
- Data consistency: keeping data consistent across services with separate databases is hard. It is often solved with the Saga pattern instead of traditional ACID transactions.
- Operational overhead: you need solid DevOps practices: containers (Docker), orchestration (Kubernetes), central logging, monitoring and tracing.
- Debugging: following a request through many services requires distributed tracing tools.
- Testing: you need contract tests and some end-to-end tests in addition to unit tests.
12. Essential Microservices Patterns
| Pattern | Problem it solves |
|---|---|
| API Gateway | One entry point for clients instead of calling every service directly |
| Service Discovery and Service Registry | Finding services whose addresses change |
| Database per Service | Keeping services independent at the data level |
| Aggregator | Combining data from several services into one response |
| Circuit Breaker | Stopping calls to a failing service |
| Retry and Timeout | Handling short failures and slow responses |
| Bulkhead | Stopping one slow dependency from using up all resources |
| Rate Limiting | Protecting services from too much traffic |
| Saga | Keeping data consistent across services without one transaction |
| Sidecar | Adding features such as logging or proxies next to a service |
| Domain-Driven Design | Finding good boundaries between services |
13. When to Use Microservices, and When Not To
| Microservices are a good fit when… | A monolith is usually better when… |
|---|---|
| The system is large and complex, with clear business areas | The application is small or simple |
| Many teams need to work and release independently | One small team builds the whole product |
| Different parts need very different scaling | Traffic is moderate and even |
| You have DevOps skills: automation, containers, monitoring | Infrastructure and operations experience is limited |
| The business domain is well understood | The product is new and still changing direction quickly |
Microservices are not a silver bullet. For a startup or a simple application, a well-structured monolith is often the more practical choice, and it can be split later when there is a clear need. See Use Cases of Microservices for real-world examples.
14. Best Practices for Beginners
- Design around business capabilities, not technical layers.
- Give each service its own database; never share tables between services.
- Keep services small enough for one team, but not so small that every feature touches many services.
- Set timeouts on every remote call, and add retries and circuit breakers where needed.
- Prefer events for work that does not need an immediate answer.
- Automate everything: builds, tests, deployments and infrastructure.
- Add observability from day one: health checks, metrics, central logs and distributed tracing with correlation IDs.
- Version your APIs and keep changes backward compatible.
- Secure every service, including traffic between services.
- Start simple: begin with a few services, or a modular monolith, and split further only when needed.
15. Your Learning Roadmap
Microservices is a large topic. A good order for learning it on this site:
- Monolithic vs Microservices Architecture: understand the trade-offs.
- Domain-Driven Design: learn how to find service boundaries.
- Communication in Microservices: synchronous and asynchronous styles.
- API Gateway and Service Discovery: connect services together.
- Database per Service and Saga: manage data.
- Circuit Breaker, Retry and Bulkhead: build resilience.
- Event-Driven Architecture: decouple services with events.
16. Summary
Microservices architecture builds an application from small, independent services, each owning one business capability and its own data, and communicating through APIs and events. It brings agility, targeted scaling, resilience and team independence, but also distributed complexity, harder data consistency and higher operational demands. With Spring Boot you can build a focused service in a few classes, connect services with RestClient or Kafka, and run them together with Docker. Use microservices for large, complex systems with several teams, and start with a well-structured monolith when the application or team is small. Continue with Monolithic vs Microservices Architecture to take the next step.
17. Frequently Asked Questions (FAQ)
Q1. What are microservices in simple words?
Microservices are a way of building an application as a set of small, separate programs, each responsible for one business job, such as payments or product search. Each one runs on its own, has its own data and talks to the others over the network.
Instead of one big application that must be changed and released as a whole, you get many small pieces that teams can change, release and scale independently.
Q2. Does "micro" mean each service must have very little code?
No. “Micro” refers to the scope of responsibility, not lines of code. A service should cover one business capability completely and be small enough for one team to own and understand.
Services that are too tiny cause the opposite problem: every feature needs changes in many services, and they spend most of their time calling each other.
Q3. What should I learn before starting with microservices?
- A backend framework, such as Spring Boot, and how to build REST APIs.
- Databases and basic SQL.
- HTTP and JSON fundamentals.
- Git and a build tool such as Maven or Gradle.
- Docker basics, to run services and their dependencies.
After that, learn the patterns step by step: API gateway, service discovery, resilience, messaging and data management.
Q4. Do I have to use Spring Boot for microservices?
No. Microservices are an architecture style, not a framework. In Java, Spring Boot is the most popular choice, but Quarkus and Micronaut are also common. Other languages work too, such as Go, Node.js, Python or .NET.
Because services communicate through standard protocols like HTTP and messaging, different services in the same system can even use different languages.
Q5. What is the difference between microservices and SOA?
Both split applications into services, but with different philosophies:
| SOA | Microservices | |
|---|---|---|
| Service size | Often large, enterprise-wide services | Small, focused on one capability |
| Communication | Often a central enterprise service bus (ESB) with logic inside | Simple protocols; “smart services, dumb pipes” |
| Data | Often shared databases | Database per service |
| Deployment | Often deployed together | Deployed independently |
Q6. Are Docker and Kubernetes required for microservices?
Not strictly, but they are the most common way to run them. Docker packages each service with everything it needs, so it runs the same everywhere. Kubernetes runs many containers in production: it restarts failed services, scales them and balances traffic.
Alternatives include managed platforms such as AWS ECS, Azure Container Apps or Google Cloud Run, which give you many of the same benefits with less setup.
Q7. Can two microservices share one database?
They should not. If two services read and write the same tables, a change by one team can silently break the other, and the services can no longer be deployed or scaled independently.
Each service should own its data. Others get it through the owner’s API or keep their own copy updated through events. Sharing one database server with separate schemas per service is acceptable if no service touches another’s schema.
Q8. Can microservices share code and libraries?
Sharing technical libraries is fine, for example logging setup, security helpers or common error formats.
Avoid sharing business models, such as one Customer class used by every service. When it changes, every service must be rebuilt and redeployed together, which brings back monolith-style coupling. Let each service define its own small model of the data it uses.
Q9. How does login work across many microservices?
Usually with tokens. The user logs in once with an identity provider, such as Keycloak, Okta or a login service, and receives a token, often a JWT.
- The API gateway checks the token on every request.
- The token is passed on to the services, which read the user’s identity and roles from it.
- Each service still checks its own permission rules, such as “customers see only their own orders”.
Q10. How do I manage configuration for many services?
- Keep defaults in each service’s
application.yml. - Override per environment with environment variables, which works everywhere, including Docker and Kubernetes.
- For many services, use a central store such as Spring Cloud Config, Kubernetes ConfigMaps, or a cloud parameter store.
- Keep secrets, such as passwords and API keys, in a secret manager, never in Git.
Q11. What is observability, and why do microservices need it?
Observability means being able to understand what is happening inside a running system from the outside. It rests on three pillars:
- Logs: what happened, collected in one central place.
- Metrics: numbers over time, such as request rate, errors and response times.
- Traces: the path of one request through every service it touched.
With dozens of services, you cannot log in to each server to look for problems, so observability is essential from day one.
Q12. What kinds of tests do microservices need?
- Unit tests for business logic inside each service.
- Integration tests with real dependencies, such as a database started by Testcontainers.
- Contract tests, for example with Spring Cloud Contract or Pact, to check that services still agree on their APIs.
- A few end-to-end tests for the most important user journeys.
Rely mostly on the fast, focused tests; end-to-end tests across many services are slow and fragile.
Q13. What is the difference between a microservice and an API?
An API is an interface: the set of requests a program accepts and the responses it returns. A microservice is a whole running program, with its own code, logic and data.
A microservice usually offers an API so others can use it, but a monolith offers APIs too. Having an API does not make something a microservice.
Q14. How many microservices should an application have?
There is no right number. Start from the business: one service per clear business capability, such as orders, payments or catalog.
A helpful check is the number of teams: each service should be owned by one team, and a team can usually own a few services. Many successful systems start with a handful of services and split them only when a real need appears.
Q15. What is the difference between microservices and serverless?
- Microservices are an architecture style: small, independent services. They usually run continuously in containers.
- Serverless is a way of running code: functions, such as AWS Lambda, start on demand and you pay per use, without managing servers.
They can be combined: a microservice can be built from serverless functions. Serverless suits event-driven, spiky workloads; long-running, steady services are often cheaper as containers.
Q16. What is the twelve-factor app, and does it apply to microservices?
The twelve-factor app is a well-known set of guidelines for building cloud-ready services. A few of the most useful ones for microservices:
- Store configuration in the environment, not in the code.
- Keep services stateless; store state in databases or caches.
- Treat logs as event streams written to standard output.
- Keep development, staging and production as similar as possible.
Spring Boot applications follow most of these principles by default.
Q17. How are microservices deployed without downtime?
- Rolling updates: replace instances one at a time while the others keep serving traffic.
- Blue-green deployments: run the new version next to the old one, then switch all traffic at once, with an easy way back.
- Canary releases: send a small share of traffic to the new version first and increase it if all is well.
Health checks, backward-compatible APIs and automated pipelines make these strategies safe.
Q18. What is a service mesh, and do beginners need one?
A service mesh, such as Istio or Linkerd, places a small proxy next to every service. The proxies handle encryption between services, retries, timeouts and traffic metrics, without changes to your code.
Beginners usually do not need one. It adds significant complexity, and libraries such as Resilience4j plus an API gateway cover most needs for small and medium systems. Consider a mesh when you run many services in Kubernetes and need consistent security and traffic control.
Q19. How do I find the cause of an error that crosses several services?
- Use the trace ID or correlation ID from the failing response or log line.
- Search the central log system for that ID to see every service’s logs for the request.
- Open the trace in a tool such as Zipkin, Jaeger or Grafana Tempo to see which call failed or was slow.
- Check that service’s metrics and recent deployments.
With Spring Boot, Micrometer Tracing adds trace IDs to logs and passes them between services automatically.
Q20. What is the most common mistake when starting with microservices?
Splitting too early and too finely. Teams often break a new application into many services before they understand the business, and end up with services that share databases, call each other constantly and must be released together: a distributed monolith.
A safer path: start with a well-structured, modular application, learn the domain, and extract services along clear business boundaries when there is a real reason, such as independent scaling or team growth.