Java Spring Boot Microservices Patterns and Actuator Explained with Real-World Examples

A beginner-friendly and interview-focused guide to Strangler Pattern, CQRS, Saga Pattern, Spring Boot Actuator, custom HealthIndicators, component scanning, and PostgreSQL with Docker.

Java Spring Boot Microservices Patterns

This guide explains the main concepts with simple examples, real-world scenarios, code, common mistakes, and interview questions.

1. Strangler Pattern

The Strangler Pattern is a migration strategy used to gradually replace a legacy application with a new application or microservices instead of rewriting the entire system at once.

The idea is similar to a strangler fig tree. The new system grows around the old system, gradually taking over functionality until the legacy system can finally be removed.

Real-World Example

Suppose an old Java monolith contains Orders, Payments, Customers, and Inventory. Instead of rewriting the entire monolith, we first move the Order functionality into a new Spring Boot Order Service.

  • Order functionality is moved to a new Spring Boot service.
  • An API Gateway or reverse proxy is placed in front of the systems.
  • Requests for /orders are routed to the new Order Service.
  • Other requests continue going to the legacy monolith.
  • More functionality is gradually migrated.
  • After everything is migrated and validated, the legacy application can be removed.
Client
   |
   v
API Gateway
   |
   +---- /orders/** ------> Order Service
   |
   +---- /payments/** ---> Legacy Monolith
   |
   +---- /customers/** --> Legacy Monolith

Typical Migration

  • Phase 1: Orders are migrated.
  • Phase 2: Payments are migrated.
  • Phase 3: Inventory is migrated.
  • Phase 4: Customers are migrated.
  • Phase 5: Legacy monolith is removed.

Advantages

  • Avoids a risky big-bang rewrite.
  • Allows incremental migration.
  • The existing application continues running.
  • Smaller changes are easier to test and roll back.
  • Teams can migrate functionality independently.

Disadvantages

  • Temporary complexity because old and new systems coexist.
  • Routing becomes more complicated.
  • Data migration can be difficult.
  • Distributed transactions may become an issue.
  • The migration can take a long time.

Interview Answer

The Strangler Pattern is an incremental legacy modernization strategy. We put a routing layer such as an API Gateway in front of the legacy system and gradually move functionality into new services. The gateway routes migrated functionality to the new services while the remaining functionality continues to use the legacy system. Once everything is migrated and validated, the legacy system can be decommissioned.

Interview Questions

  • What is the Strangler Pattern?
  • Why is it safer than a big-bang rewrite?
  • How would you implement it using Spring Boot?
  • How would an API Gateway help?
  • How would you handle database migration?
  • What happens if the new service fails?
  • How would you roll back traffic to the legacy application?

2. CQRS

CQRS stands for Command Query Responsibility Segregation. It separates operations that modify data from operations that only read data.

  • Command means an operation that changes state.
  • Query means an operation that reads data.
Command
   |
   v
Write Model
   |
Write Database

Query
   |
   v
Read Model
   |
Read Database

Command Examples

  • CreateOrder
  • UpdateOrder
  • CancelOrder
  • ChangeCustomerAddress

Query Examples

  • GetOrderById
  • SearchOrders
  • GetCustomerOrders
  • GetOrderSummary

Simple Spring Boot Example

public record CreateOrderCommand(
    Long customerId,
    double amount
) {}

@PostMapping("/orders")
public void createOrder(@RequestBody CreateOrderCommand command) {
    orderCommandService.create(command);
}

@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
    return orderQueryService.getOrder(id);
}

Important Interview Point

CQRS does not require two databases. The main idea is separation of command and query responsibilities. A simple application can use the same database for both. More advanced systems can use separate write and read databases.

CQRS with Event-Driven Architecture

CreateOrder
    |
    v
Order Service
    |
    v
Write Database
    |
    v
OrderCreated Event
    |
    v
Kafka
    |
    v
Read Model / Projection
    |
    v
Read Database

Advantages

  • Read and write workloads can be optimized independently.
  • Read and write sides can scale independently.
  • Complex read models can be designed separately from transactional models.
  • It works well with event-driven architectures.

Disadvantages

  • Adds architectural complexity.
  • Separate read models can introduce eventual consistency.
  • More code and components are required.
  • Debugging can become more difficult.
  • CQRS is unnecessary for many simple CRUD applications.

Interview Answer

CQRS separates commands that modify application state from queries that retrieve application state. This allows the read and write sides to be optimized and scaled independently. CQRS can use the same database or separate read and write databases depending on the system requirements.

Interview Questions

  • What is CQRS?
  • What is the difference between a command and a query?
  • Does CQRS require two databases?
  • Why would you use CQRS?
  • What is eventual consistency in CQRS?
  • What are the disadvantages of CQRS?
  • How can CQRS work with Kafka?

3. Saga Pattern

Saga is a microservices pattern used to manage a distributed business transaction across multiple services. Instead of one global transaction, the business transaction is divided into multiple local transactions.

If one step fails, compensating transactions are executed to undo or compensate for the previous successful steps.

Real-World E-Commerce Example

Create Order
     |
     v
Reserve Inventory
     |
     v
Process Payment
     |
     v
Create Shipment
     |
     v
Order Confirmed

Failure Scenario

Order Created
     |
     v
Inventory Reserved
     |
     v
Payment FAILED
     |
     v
Release Inventory  <- Compensation
     |
     v
Cancel Order       <- Compensation

Saga Orchestration

In orchestration, a central Saga Orchestrator controls the workflow and tells each service what operation to perform.

                 Saga Orchestrator
                    /    |    \
                   v     v     v
                Order Inventory Payment
                Service Service  Service

Simple Java Example

@Service
public class OrderSaga {

    public void execute(CreateOrderRequest request) {
        boolean inventoryReserved = false;

        try {
            orderService.createOrder(request);

            inventoryClient.reserve(
                request.productId(),
                request.quantity()
            );
            inventoryReserved = true;

            paymentClient.pay(
                request.orderId(),
                request.amount()
            );

            System.out.println("Order confirmed");

        } catch (Exception e) {

            if (inventoryReserved) {
                inventoryClient.release(
                    request.productId(),
                    request.quantity()
                );
            }

            orderService.cancelOrder(request.orderId());
        }
    }
}

Choreography

In choreography, there is no central orchestrator. Services communicate using events, commonly through a message broker such as Kafka.

OrderCreated
     |
     v
   Kafka
     |
     v
Inventory Service
     |
InventoryReserved
     |
     v
   Kafka
     |
     v
Payment Service

Orchestration vs Choreography

  • Orchestration uses a central controller for the business workflow.
  • Choreography uses events and lets services react to events.
  • Orchestration is often easier to understand for complex workflows.
  • Choreography provides loose coupling but can become difficult to trace when many events are involved.

Important Interview Point

A Saga does not provide a traditional global ACID transaction across all microservices. It provides eventual consistency using local transactions and compensating actions.

Interview Questions

  • What is the Saga Pattern?
  • Why can't we simply use @Transactional across microservices?
  • What is a compensating transaction?
  • What is Saga orchestration?
  • What is Saga choreography?
  • What happens if payment succeeds but shipping fails?
  • What happens if a compensation itself fails?
  • How would you handle retries and idempotency?
  • How can Kafka be used with Saga?

4. Strangler vs CQRS vs Saga

These patterns solve different problems and can be used together.

  • Strangler Pattern: gradually migrates a legacy application.
  • CQRS: separates read and write responsibilities.
  • Saga: manages distributed business transactions.
Legacy Monolith
      |
      v
Strangler Pattern
      |
      v
Microservices
      |
      +---- CQRS for read/write separation
      |
      +---- Saga for distributed transactions

5. Spring Boot Actuator

Spring Boot Actuator provides production-ready endpoints for monitoring and managing a Spring Boot application.

  • Health information
  • Application metrics
  • Application information
  • Logger configuration
  • Environment information
  • Other operational information

Actuator Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Actuator is not included automatically by spring-boot-starter-data-jpa. JPA and Actuator are separate Spring Boot starters.

  • spring-boot-starter-data-jpa is for JPA, Hibernate, repositories, and database access.
  • spring-boot-starter-actuator is for monitoring, health checks, and operational endpoints.

Custom Actuator Base Path

management.endpoints.web.base-path=/manage
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

Because the base path is changed to /manage, the health endpoint becomes /manage/health instead of /actuator/health.

6. Custom HealthIndicator

Spring Boot allows us to create custom health checks by implementing HealthIndicator and registering the class as a Spring bean.

Database Health Checker

package learning.java.notes.actuatorDetails;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class DbHealthChecker implements HealthIndicator {

    @Override
    public Health health() {
        boolean isDbUp = checkDbStatus();

        return isDbUp
                ? Health.up()
                    .withDetail("DB", "Available")
                    .build()
                : Health.down()
                    .withDetail("DB", "Not Available")
                    .build();
    }

    private boolean checkDbStatus() {
        return true;
    }
}

Cache Health Checker

package learning.java.notes.actuatorDetails;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CacheHealthChecker implements HealthIndicator {

    @Override
    public Health health() {
        boolean isCacheUp = checkCacheStatus();

        return isCacheUp
                ? Health.up()
                    .withDetail("Cache", "Available")
                    .build()
                : Health.down()
                    .withDetail("Cache", "Not Available")
                    .build();
    }

    private boolean checkCacheStatus() {
        return false;
    }
}

Expected Result

If checkCacheStatus returns false and the CacheHealthChecker bean is registered, the cache component is DOWN and the overall health status normally becomes DOWN.

{
  "status": "DOWN",
  "components": {
    "cacheHealthChecker": {
      "status": "DOWN",
      "details": {
        "Cache": "Not Available"
      }
    },
    "dbHealthChecker": {
      "status": "UP",
      "details": {
        "DB": "Available"
      }
    }
  }
}

7. Why Custom HealthIndicator Was Not Appearing

If the Actuator response only shows built-in indicators such as db, diskSpace, and ping, but does not show custom indicators, the most likely problem is component scanning.

A class annotated with @Component is only discovered if Spring's component scan reaches that package.

Example:

Main application:
learning.java.notes.NotesApplication

Custom class:
actuatorDetails.CacheHealthChecker

The package actuatorDetails is outside learning.java.notes, so the default component scan does not find it.

8. Default Component Scanning

By default, @SpringBootApplication performs component scanning starting from the package containing the application class and recursively scans its subpackages.

package com.example.app;

@SpringBootApplication
public class Application {
}

Scanned:
com.example.app
com.example.app.controller
com.example.app.service
com.example.app.repository
com.example.app.actuatorDetails

Not automatically scanned:
com.example.other
actuatorDetails
com.other

Best Practice

Normally, keep the main application class in a top-level package and place controllers, services, repositories, configuration classes, and other components underneath it.

learning.java.notes
|
+-- NotesApplication.java
+-- controller
+-- service
+-- repository
+-- actuatorDetails

9. Using ComponentScan for an External Package

If components must remain outside the default package hierarchy, explicitly configure component scanning.

package learning.java.notes;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({
    "learning.java.notes",
    "actuatorDetails"
})
public class NotesApplication {

    public static void main(String[] args) {
        SpringApplication.run(NotesApplication.class, args);
    }
}

Real-World ComponentScan Example

A company may have a shared internal library containing reusable security, audit, logging, or monitoring components. The application package and shared library package may be separate. ComponentScan can explicitly include the shared package.

@SpringBootApplication
@ComponentScan({
    "com.company.orders",
    "com.company.common"
})
public class OrderApplication {
}

Interview Answer

By default, Spring Boot scans the package containing the @SpringBootApplication class and all of its subpackages. If a required Spring component is outside that hierarchy, we can use @ComponentScan with the required package or use another explicit bean registration mechanism.

10. PostgreSQL with Docker on Windows

If PostgreSQL is running inside Docker, you do not need the Windows psql command if psql is not installed on Windows. You can execute psql inside the PostgreSQL container.

Find the PostgreSQL Container

docker ps

Look at the NAMES column to find the actual PostgreSQL container name.

Connect to PostgreSQL

docker exec -it <container-name> psql -U admin -d searchdb

The -U option specifies the PostgreSQL username. The -d option specifies the database name.

Run a SELECT Query

SELECT name
FROM pg_timezone_names
WHERE name LIKE '%Kolkata%';

Run an UPDATE Query

UPDATE employee
SET name = 'Rahul'
WHERE id = 1;

Run SQL Directly from PowerShell

docker exec -it <container-name> psql -U admin -d searchdb -c "SELECT * FROM employee;"

PostgreSQL Timezone

The correct current city name is Kolkata. The PostgreSQL timezone identifier commonly used for Kolkata is Asia/Kolkata.

SELECT name
FROM pg_timezone_names
WHERE name LIKE '%Kolkata%';
ZoneId.of("Asia/Kolkata");

11. Spring Boot Maven Dependencies

For a Spring Boot application using Web, Validation, JPA, PostgreSQL, Lombok, Test, and Actuator, the dependencies can be structured as follows.

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

Spring Boot dependency management normally provides compatible versions for Spring Boot managed dependencies, so versions do not need to be manually specified for these starters.

The explicit jakarta.persistence-api dependency is normally unnecessary when spring-boot-starter-data-jpa is already present.

12. Understanding Transitive Dependency Warnings

Security scanners such as Checkmarx may report vulnerabilities in transitive dependencies. A transitive dependency is a library brought into your application indirectly by another dependency.

Your application
     |
     v
Spring Boot Starter
     |
     +---- Spring Boot
     |
     +---- Jackson
     |
     +---- Logback

If a scanner reports a vulnerable Jackson or Logback version, it does not necessarily mean you directly added that library.

  • First identify which dependency brings the vulnerable library.
  • Prefer upgrading Spring Boot to a release that manages a fixed compatible version.
  • Avoid randomly overriding transitive versions unless there is a specific reason.
  • Use mvn dependency:tree to inspect the dependency graph.
mvn dependency:tree

13. Important Actuator Interview Questions

  • What is Spring Boot Actuator?
  • What is the default Actuator base path?
  • How do you expose Actuator endpoints?
  • How do you change the Actuator base path?
  • How do you create a custom HealthIndicator?
  • What is the difference between HealthIndicator and a normal service bean?
  • Why would a custom HealthIndicator not appear?
  • What does management.endpoint.health.show-details do?
  • How do you protect Actuator endpoints in production?

14. Important Component Scanning Interview Questions

  • What is component scanning?
  • What package does Spring Boot scan by default?
  • Does Spring scan packages outside the application package?
  • How does @SpringBootApplication relate to @ComponentScan?
  • When would you use @ComponentScan explicitly?
  • How would you scan a shared library package?
  • Why is a class annotated with @Component sometimes not created as a bean?

15. Important Microservices Interview Questions

  • What is the Strangler Pattern?
  • What is CQRS?
  • What is Saga?
  • What is the difference between Saga orchestration and choreography?
  • What is a compensating transaction?
  • Why can't @Transactional normally span multiple independent microservices?
  • What is eventual consistency?
  • How would you migrate a monolith to microservices?
  • How would you handle failures between microservices?
  • How would you make Saga operations idempotent?

16. Quick Interview Revision

  • Strangler Pattern = gradually replace a legacy system.
  • CQRS = separate commands from queries.
  • Saga = manage distributed business transactions using local transactions and compensations.
  • Saga Orchestration = central orchestrator controls the workflow.
  • Saga Choreography = services communicate through events.
  • Actuator = application monitoring and operational endpoints.
  • HealthIndicator = custom health check for Actuator.
  • @Component = registers a class as a Spring bean when component scanning discovers it.
  • @SpringBootApplication = includes configuration, auto-configuration, and component scanning.
  • Default component scan = application package plus its subpackages.
  • @ComponentScan = explicitly scans additional packages.
  • JPA starter does not include Actuator.
  • PostgreSQL Docker connection can use docker exec and psql inside the container.
  • Kolkata timezone = Asia/Kolkata.

17. One-Minute Interview Summary

If an interviewer asks about these concepts together, explain them based on the problem they solve. Use Strangler Pattern when modernizing a legacy monolith incrementally. Use CQRS when read and write responsibilities have significantly different requirements. Use Saga when one business transaction spans multiple microservices and cannot be handled by one normal database transaction. Use Actuator for application monitoring and health checks. Use custom HealthIndicator implementations when you need application-specific health checks. Spring Boot automatically scans the application package and its subpackages, while @ComponentScan is useful when required components exist outside that package hierarchy.