Sitemap

Microservices Interview Questions for Java Developers 2026

21 min readApr 23, 2026

--

Every senior Java interview in 2026 includes microservices questions. Here is the complete guide — architecture, Spring Cloud, patterns, Kafka, Kubernetes, and everything in between.

Press enter or click to view image in full size

If you are interviewing for a Java backend role in 2026, you will be asked about microservices. Not occasionally — in almost every interview at product companies, startups, and enterprises alike. Microservices have become the dominant architectural style for Java backend systems, and interviewers expect developers to understand not just how to build them but why they are designed the way they are and what breaks when you get them wrong.

This guide covers every microservices question asked in Java developer interviews — from the fundamentals to advanced distributed systems patterns. Each answer goes to the depth interviewers expect at mid-level and senior roles.

📘 [Java Backend Interview Questions]

It covers 300+ questions across all experience levels, with detailed explanations, common follow-up questions, and the reasoning interviewers are actually looking for.

📘 [Complete Backend Interview Questions]

It covers 1000+ core Java, collections, concurrency, JVM internals, Spring, design patterns, and system design — with the kind of detailed explanations that don’t just help you pass interviews, but make you a better Java developer.

Section 1: Fundamentals

Q1. What are microservices? How are they different from a monolith?

A monolith is a single deployable unit containing all application functionality — UI, business logic, and data access — tightly coupled and sharing a single database. All modules run in the same process.

Microservices decompose the application into small, independently deployable services — each owning a specific business capability, running in its own process, communicating over a network, and managing its own data.

MONOLITH:                          MICROSERVICES:
┌─────────────────────┐ ┌──────────┐ ┌──────────┐
│ Order Management │ │ Order │ │ Payment │
│ Payment Processing │ │ Service │ │ Service │
│ User Management │ → └──────────┘ └──────────┘
│ Inventory │ ┌──────────┐ ┌──────────┐
│ Notifications │ │ User │ │Inventory │
│ [Single Database] │ │ Service │ │ Service │
└─────────────────────┘ └──────────┘ └──────────┘

Key differences:

Aspect Monolith Microservices Deployment Single unit Independent per service Scaling Scale everything Scale individual services Technology Single stack Polyglot possible Database Shared Per-service (ideally) Failure One failure can crash all Isolated failures Team Single team Independent teams per service Complexity Simple to develop initially Complex operationally

Q2. What are the advantages and disadvantages of microservices?

Advantages:

  • Independent deployment: Deploy the Order Service without touching Payment Service. Release cycles are faster and less risky.
  • Independent scalability: Scale only the services under load. Payment Service gets 10x traffic during sales — scale only that.
  • Technology flexibility: Use Java for compute-heavy services, Node.js for real-time, Python for ML — best tool for each job.
  • Fault isolation: A failure in the Notification Service does not bring down Order Processing.
  • Smaller codebase per service: Easier to understand, test, and maintain.
  • Team autonomy: Each team owns their service end to end — reduces coordination overhead.

Disadvantages — the ones interviewers want to hear:

  • Distributed system complexity: Network calls fail. Latency increases. Partial failures are harder to handle than in-process calls.
  • Data consistency challenges: No shared database means no ACID transactions across services. You must handle eventual consistency explicitly.
  • Operational overhead: Dozens of services means dozens of deployments, log streams, monitoring dashboards, and failure points.
  • Service discovery and communication: Services need to find each other dynamically. Versioning APIs becomes critical.
  • Testing complexity: Integration tests must spin up multiple services. Contract testing becomes essential.
  • Debugging difficulty: A request spanning 8 services produces stack traces across 8 log files — distributed tracing is mandatory.

The honest answer interviewers respect: Microservices solve organizational and scalability problems at the cost of operational complexity. They are the wrong choice for small teams and early-stage products. Start with a modular monolith; extract services when you have a clear reason to.

Q3. What is the difference between microservices and SOA (Service-Oriented Architecture)?

Both decompose applications into services — but the philosophy and implementation differ significantly.

Aspect SOA Microservices Communication Enterprise Service Bus (ESB) Lightweight (REST, gRPC, messaging) Service size Coarse-grained Fine-grained Data Shared enterprise data store Per-service database Governance Centralized, heavy Decentralized, lightweight Protocols SOAP, WS-* REST, gRPC, Kafka Deployment Often shared platform Independent containers Philosophy Reuse Single responsibility

SOA was heavyweight — ESBs became bottlenecks and single points of failure. Microservices moved the intelligence to the services themselves (smart endpoints, dumb pipes) and embraced lightweight protocols.

Q4. What is the 12-Factor App methodology? How does it relate to microservices?

The 12-Factor App is a methodology for building software-as-a-service applications that are portable, scalable, and maintainable. It directly informs how microservices should be built.

The 12 factors — what interviewers actually ask about:

I. Codebase: One codebase per service, tracked in version control.

II. Dependencies: Explicitly declare all dependencies (pom.xml, build.gradle). Never rely on system-wide packages.

III. Config: Store config in environment variables — not hardcoded, not in code. Separation of config from code enables the same artifact to run in dev/staging/prod.

# application.yml — externalized config
spring:
datasource:
url: ${DB_URL} # from environment
username: ${DB_USER}
password: ${DB_PASS}

IV. Backing services: Treat databases, queues, caches as attached resources. Swap a local MySQL for Amazon RDS with a config change, no code change.

V. Build, release, run: Strictly separate build (compile), release (build + config), and run (execute) stages. Never change code at runtime.

VI. Processes: Execute the app as stateless processes. No sticky sessions. State lives in a backing service (Redis, DB).

VII. Port binding: Services export HTTP by binding to a port — not deployed into a web server container.

VIII. Concurrency: Scale out via process model. Add more instances, not bigger machines.

IX. Disposability: Fast startup (seconds, not minutes). Graceful shutdown. Designed for crash recovery.

X. Dev/prod parity: Keep development, staging, and production as similar as possible.

XI. Logs: Treat logs as event streams. Write to stdout — let the platform aggregate them (ELK, Splunk).

XII. Admin processes: Run admin tasks (migrations, scripts) as one-off processes in the same environment.

Section 2: Service Communication

Q5. What is the difference between synchronous and asynchronous communication in microservices?

Synchronous communication: The caller sends a request and waits for a response. HTTP/REST and gRPC are synchronous. Simple to reason about but creates temporal coupling — if the downstream service is slow or down, the caller is affected.

// Synchronous REST call — caller blocks until response
@Service
public class OrderService {
private final RestTemplate restTemplate;
    public PaymentResult processPayment(PaymentRequest request) {
// If PaymentService is down, this call fails
return restTemplate.postForObject(
"http://payment-service/payments",
request,
PaymentResult.class
);
}
}

Asynchronous communication: The caller sends a message and does not wait — it continues processing. Messaging systems (Kafka, RabbitMQ) enable async communication. Decouples services temporally — downstream unavailability does not affect the caller.

// Asynchronous Kafka producer — fire and continue
@Service
public class OrderService {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
    public void placeOrder(Order order) {
saveOrder(order);
// Publish event — Payment Service processes when ready
kafkaTemplate.send("order-placed", new OrderEvent(order));
// Don't wait — return immediately
}
}

When to use which:

  • Use synchronous when you need an immediate response (user-facing APIs, queries)
  • Use asynchronous when the operation can be eventual (notifications, analytics, background processing)
  • Use async to break cascade failures — if Payment is slow, Order Service keeps accepting orders

Q6. What is gRPC? When would you use it over REST?

gRPC is a high-performance RPC (Remote Procedure Call) framework from Google. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport.

// payment.proto
service PaymentService {
rpc ProcessPayment(PaymentRequest) returns (PaymentResponse);
rpc StreamTransactions(TransactionFilter) returns (stream Transaction);
}
message PaymentRequest {
string order_id = 1;
double amount = 2;
string currency = 3;
}
// Java gRPC client — auto-generated from proto
PaymentServiceGrpc.PaymentServiceBlockingStub stub =
PaymentServiceGrpc.newBlockingStub(channel);
PaymentResponse response = stub.processPayment(
PaymentRequest.newBuilder()
.setOrderId("ORD-123")
.setAmount(99.99)
.setCurrency("USD")
.build()
);

gRPC advantages over REST:

  • Performance: Protobuf is 3–10x smaller than JSON. HTTP/2 multiplexing reduces connection overhead.
  • Streaming: Supports server streaming, client streaming, and bidirectional streaming natively.
  • Strong typing: Proto files define exact contracts — no schema ambiguity.
  • Code generation: Client and server stubs generated automatically in any language.

When to use gRPC: Internal service-to-service communication where performance matters, polyglot services needing strong contracts, streaming use cases (real-time data, large file transfers).

When to stick with REST: Public APIs (gRPC browser support is limited), teams unfamiliar with protobuf, simple CRUD with no performance concerns.

Q7. What is an API Gateway? What responsibilities does it have?

An API Gateway is the single entry point for all client requests. It sits in front of your microservices and handles cross-cutting concerns so individual services don’t have to.

Client → API Gateway → [User Service]
→ [Order Service]
→ [Payment Service]
→ [Inventory Service]

Responsibilities of an API Gateway:

Routing: Direct requests to the correct microservice based on path, headers, or other criteria.

Authentication and Authorization: Validate JWT tokens or API keys before requests reach services. Services trust the gateway’s verification.

Rate Limiting: Limit requests per client to prevent abuse — 100 requests/minute per API key.

Load Balancing: Distribute requests across multiple instances of a service.

SSL Termination: Handle HTTPS at the gateway, communicate with services over HTTP internally.

Request/Response Transformation: Aggregate responses from multiple services into one response (Backend for Frontend pattern).

Circuit Breaking: Stop routing to failing services, return fallback responses.

Logging and Monitoring: Centralized request/response logging, latency tracking.

// Spring Cloud Gateway route configuration
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Service", "order")
.circuitBreaker(c -> c.setName("orderCB").setFallbackUri("forward:/fallback")))
.uri("lb://order-service")) // lb = load balanced
.build();
}

Popular API Gateways for Java: Spring Cloud Gateway, Kong, AWS API Gateway, Netflix Zuul (legacy).

Q8. What is Service Discovery? Explain client-side vs server-side discovery.

Service Discovery solves the problem of services finding each other when instances start, stop, and move dynamically in a containerized environment.

Client-side discovery (Eureka pattern): The client queries a Service Registry (like Netflix Eureka) to get the list of available instances, then chooses one using a load balancing algorithm.

// Service registers itself with Eureka on startup
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApplication { }
// application.yml
eureka:
client:
serviceUrl:
defaultZone: http://eureka-server:8761/eureka/
instance:
preferIpAddress: true
// Client discovers and calls using @LoadBalanced RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate() { return new RestTemplate(); }
// Use service name instead of host:port
restTemplate.getForObject("http://payment-service/payments/123", Payment.class);
// Spring Cloud resolves "payment-service" to an actual instance

Server-side discovery (Kubernetes pattern): The client sends requests to a load balancer (Kubernetes Service, AWS ELB). The load balancer queries the registry and forwards. The client knows nothing about discovery.

# Kubernetes Service — acts as server-side load balancer
apiVersion: v1
kind: Service
metadata:
name: payment-service
spec:
selector:
app: payment
ports:
- port: 8080

Client-Side Server-Side Examples Eureka + Ribbon Kubernetes, AWS ELB Complexity Client must implement LB logic Simpler client Flexibility Full control over LB Delegated to infrastructure Coupling Client knows about registry Client just calls a URL

Section 3: Resilience Patterns

Q9. What is the Circuit Breaker pattern? How does Resilience4j implement it?

The Circuit Breaker prevents cascade failures. When a downstream service is failing, continuing to call it wastes resources and slows your service. The circuit breaker trips open after a threshold of failures and returns fallback responses immediately — without attempting the call.

Three states:

CLOSED (normal) → failures exceed threshold → OPEN (failing fast)
↑ |
└──── half-open calls succeed ←── HALF-OPEN ←─┘
(testing recovery)
@Service
public class PaymentService {
    @CircuitBreaker(name = "payment", fallbackMethod = "paymentFallback")
@Retry(name = "payment")
@TimeLimiter(name = "payment")
public CompletableFuture<PaymentResult> processPayment(PaymentRequest request) {
return CompletableFuture.supplyAsync(() ->
paymentClient.process(request)
);
}
// Fallback — called when circuit is open
public CompletableFuture<PaymentResult> paymentFallback(
PaymentRequest request, Exception ex) {
log.warn("Payment service unavailable, using fallback", ex);
return CompletableFuture.completedFuture(
PaymentResult.pending(request.getOrderId())
);
}
}
# application.yml — Resilience4j configuration
resilience4j:
circuitbreaker:
instances:
payment:
slidingWindowSize: 10 # evaluate last 10 calls
failureRateThreshold: 50 # open if 50%+ fail
waitDurationInOpenState: 10s # wait before trying HALF-OPEN
permittedNumberOfCallsInHalfOpenState: 3
retry:
instances:
payment:
maxAttempts: 3
waitDuration: 500ms
retryExceptions:
- java.net.ConnectException
timelimiter:
instances:
payment:
timeoutDuration: 2s

Q10. What is the Bulkhead pattern? How does it prevent cascade failures?

The Bulkhead pattern isolates elements of a system into pools so that if one fails, the others continue to function. Named after ship bulkheads that prevent flooding from spreading.

Thread pool bulkhead: Give each downstream service its own thread pool. A slow Payment Service cannot exhaust threads needed for Inventory Service.

@Bulkhead(name = "payment", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<PaymentResult> processPayment(PaymentRequest req) {
return CompletableFuture.supplyAsync(() -> callPaymentService(req));
}
@Bulkhead(name = "inventory", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<InventoryResult> checkInventory(String productId) {
return CompletableFuture.supplyAsync(() -> callInventoryService(productId));
}
resilience4j:
thread-pool-bulkhead:
instances:
payment:
maxThreadPoolSize: 10
coreThreadPoolSize: 5
queueCapacity: 20 # requests beyond this are rejected immediately
inventory:
maxThreadPoolSize: 5
coreThreadPoolSize: 2
queueCapacity: 10

Semaphore bulkhead: Limit concurrent calls without a separate thread pool — lighter weight, used for non-blocking calls.

Q11. What is the Saga pattern? How do you handle distributed transactions?

In a microservices architecture, each service has its own database. Traditional ACID transactions spanning multiple services are not possible. The Saga pattern manages distributed transactions as a sequence of local transactions — each service performs its own transaction and publishes an event or calls the next service.

Two types:

Choreography-based Saga: Services communicate through events. No central coordinator.

Order Service → [order-placed event] → Payment Service
Payment Service → [payment-completed event] → Inventory Service
Inventory Service → [inventory-reserved event] → Shipping Service
// Compensation if payment fails:
Payment Service → [payment-failed event] → Order Service
Order Service → [order-cancelled event] (compensation)
// Order Service publishes event
@Transactional
public Order placeOrder(OrderRequest request) {
Order order = orderRepository.save(new Order(request, OrderStatus.PENDING));
eventPublisher.publish(new OrderPlacedEvent(order)); // Kafka
return order;
}
// Payment Service listens and responds
@KafkaListener(topics = "order-placed")
public void handleOrderPlaced(OrderPlacedEvent event) {
try {
PaymentResult result = processPayment(event.getOrderId(), event.getAmount());
eventPublisher.publish(new PaymentCompletedEvent(event.getOrderId()));
} catch (Exception e) {
eventPublisher.publish(new PaymentFailedEvent(event.getOrderId()));
}
}

Orchestration-based Saga: A central orchestrator (Saga Orchestrator) tells each service what to do and handles compensations.

@Service
public class OrderSagaOrchestrator {
    public void execute(OrderRequest request) {
SagaTransaction saga = SagaTransaction.begin()
.step("create-order",
() -> orderService.create(request),
(orderId) -> orderService.cancel(orderId)) // compensation
.step("process-payment",
(orderId) -> paymentService.charge(orderId, request.getAmount()),
(orderId) -> paymentService.refund(orderId)) // compensation
.step("reserve-inventory",
(orderId) -> inventoryService.reserve(orderId),
(orderId) -> inventoryService.release(orderId)) // compensation
.execute();
}
}

Choreography vs Orchestration:

Choreography Orchestration Coordination Decentralized (events) Centralized (orchestrator) Coupling Loose Tighter to orchestrator Visibility Hard to track flow Clear in orchestrator Complexity Grows with services Concentrated in one place Use case Simple flows Complex multi-step transactions

Q12. What is the Outbox Pattern? Why is it important for event reliability?

Consider this code:

@Transactional
public Order placeOrder(OrderRequest request) {
Order order = orderRepository.save(order);
kafkaTemplate.send("order-placed", event); // What if Kafka is down?
return order;
}

If Kafka is down or the message send fails after the DB commit, the order is saved but the event is never published. Other services never know the order was placed. Data inconsistency.

The Outbox Pattern solves this by writing the event to an outbox table in the same database transaction as the business data. A separate process reads the outbox and publishes to Kafka.

@Transactional
public Order placeOrder(OrderRequest request) {
Order order = orderRepository.save(new Order(request));
    // Same transaction — atomically saved with the order
outboxRepository.save(new OutboxEvent(
"OrderPlaced",
order.getId(),
serialize(new OrderPlacedEvent(order))
));
return order; // Kafka publish happens separately — guaranteed
}
// Outbox publisher — runs as a scheduled job or CDC (Change Data Capture)
@Scheduled(fixedDelay = 1000)
public void publishOutboxEvents() {
List<OutboxEvent> unpublished = outboxRepository.findUnpublished();
for (OutboxEvent event : unpublished) {
kafkaTemplate.send(event.getTopic(), event.getPayload());
outboxRepository.markPublished(event.getId());
}
}

Production-grade alternative: Use Debezium for Change Data Capture (CDC) — Debezium watches your database transaction log and publishes events to Kafka automatically. No polling, minimal latency, guaranteed at-least-once delivery.

Section 4: Spring Cloud

Q13. What is Spring Cloud? Name the key components.

Spring Cloud is a suite of tools built on Spring Boot that provides solutions for common microservices patterns:

Component Purpose Spring Cloud Gateway API Gateway — routing, filtering, rate limiting Spring Cloud Netflix Eureka Service Registry and Discovery Spring Cloud Config Centralized configuration management Spring Cloud OpenFeign Declarative REST client Spring Cloud Circuit Breaker Circuit breaker abstraction (Resilience4j) Spring Cloud Sleuth / Micrometer Tracing Distributed tracing Spring Cloud Stream Messaging abstraction (Kafka, RabbitMQ) Spring Cloud Kubernetes Kubernetes integration

Q14. What is Feign? How does it simplify service-to-service calls?

OpenFeign is a declarative REST client. Instead of writing RestTemplate boilerplate, you define an interface and Spring generates the implementation.

// Define the client interface
@FeignClient(
name = "payment-service",
fallbackFactory = PaymentClientFallbackFactory.class
)
public interface PaymentClient {
    @PostMapping("/payments")
PaymentResult processPayment(@RequestBody PaymentRequest request);
@GetMapping("/payments/{id}")
Payment getPayment(@PathVariable String id);
@GetMapping("/payments")
List<Payment> getPaymentsByOrder(@RequestParam String orderId);
}
// Use it like a local service call
@Service
public class OrderService {
private final PaymentClient paymentClient;
public void processOrder(Order order) {
PaymentResult result = paymentClient.processPayment(
new PaymentRequest(order.getId(), order.getAmount())
);
}
}
// Fallback factory for circuit breaker integration
@Component
public class PaymentClientFallbackFactory
implements FallbackFactory<PaymentClient> {
    @Override
public PaymentClient create(Throwable cause) {
return new PaymentClient() {
@Override
public PaymentResult processPayment(PaymentRequest request) {
log.error("Payment service unavailable", cause);
return PaymentResult.pending(request.getOrderId());
}
};
}
}

Feign with Resilience4j, logging, and retry — all through configuration:

feign:
circuitbreaker:
enabled: true
client:
config:
payment-service:
connectTimeout: 2000
readTimeout: 5000
loggerLevel: FULL

Q15. What is Spring Cloud Config? How does centralized configuration work?

Spring Cloud Config provides a centralized configuration server that serves configuration properties to all microservices. Configuration is stored in a Git repository (or file system, Vault) and served over HTTP.

Git Repo (config source)
↓
Config Server (Spring Cloud Config)
↓
All Microservices pull their config on startup
// Config Server
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication { }
# Config Server application.yml
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/config-repo
search-paths: '{application}'
default-label: main
# order-service/application.yml in Git repo
database:
host: prod-db.internal
pool-size: 20
payment:
timeout: 3000
retry-attempts: 3
# Order Service bootstrap.yml
spring:
application:
name: order-service
config:
import: "configserver:http://config-server:8888"

Refresh without restart: With @RefreshScope and Spring Cloud Bus (Kafka/RabbitMQ), push configuration updates to all instances without restarting.

@RestController
@RefreshScope // re-initialized when /actuator/refresh is called
public class OrderController {
@Value("${payment.timeout}")
private int paymentTimeout; // updated without restart
}

Section 5: Event-Driven Architecture and Kafka

Q16. What is Kafka? Why is it widely used in microservices?

Apache Kafka is a distributed event streaming platform. It stores events in topics (ordered, immutable logs) and delivers them to consumers — reliably, at massive scale, with replay capability.

Get Vighneshwar Bhat’s stories in your inbox

Join Medium for free to get updates from this writer.

Why Kafka in microservices:

  • Decoupling: Producer does not know or care about consumers. New consumers can be added without changing producers.
  • Durability: Events are persisted on disk — messages survive consumer downtime.
  • Replayability: Consumers can reset their offset and reprocess events from any point.
  • Scale: Millions of events per second across partitions.
  • Event sourcing: Kafka log is the source of truth — rebuild state by replaying events.
// Producer — Spring Kafka
@Service
public class OrderEventPublisher {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
    public void publishOrderPlaced(Order order) {
OrderEvent event = new OrderEvent(
order.getId(),
"ORDER_PLACED",
order.getAmount(),
Instant.now()
);
kafkaTemplate.send("orders", order.getId(), event) // key = orderId ensures ordering per order
.whenComplete((result, ex) -> {
if (ex != null) log.error("Failed to publish event", ex);
else log.info("Published to partition {}", result.getRecordMetadata().partition());
});
}
}
// Consumer — Spring Kafka
@Service
public class PaymentEventConsumer {
    @KafkaListener(
topics = "orders",
groupId = "payment-service",
containerFactory = "kafkaListenerContainerFactory"
)
public void handleOrderPlaced(
@Payload OrderEvent event,
@Header(KafkaHeaders.RECEIVED_PARTITION) int partition,
Acknowledgment ack) {
try {
processPayment(event);
ack.acknowledge(); // manual commit after successful processing
} catch (RetryableException e) {
// Don't ack — message will be redelivered
throw e;
} catch (NonRetryableException e) {
ack.acknowledge(); // ack and send to dead letter topic
deadLetterPublisher.publish(event, e);
}
}
}

Q17. What is idempotency? Why is it critical in event-driven microservices?

In event-driven systems, messages can be delivered more than once (at-least-once delivery guarantee). An operation is idempotent if processing it multiple times produces the same result as processing it once.

Without idempotency:

Order placed → Payment charged $100
Kafka delivers message again (network issue) → Payment charged $100 AGAIN
Customer charged twice — production incident

With idempotency:

@KafkaListener(topics = "orders")
@Transactional
public void handleOrderPlaced(OrderEvent event) {
// Idempotency check — have we processed this event before?
if (processedEventRepository.exists(event.getEventId())) {
log.info("Duplicate event {}, skipping", event.getEventId());
return;
}
    // Process and record atomically
paymentService.charge(event.getOrderId(), event.getAmount());
processedEventRepository.save(new ProcessedEvent(event.getEventId()));
}

Idempotency strategies:

  • Idempotency keys: Store processed event/request IDs in DB, skip duplicates
  • Conditional updates: UPDATE payments SET status='processed' WHERE id=? AND status='pending' — only processes if not already done
  • Natural idempotency: Designing operations that are inherently safe to repeat (set value = X is idempotent, increment by 1 is not)

Section 6: Security

Q18. How do you implement authentication and authorization in microservices?

The recommended pattern is centralized authentication with distributed authorization.

OAuth 2.0 + JWT flow:

User → API Gateway → Auth Service → returns JWT
User → API Gateway (with JWT) → validates token → routes to service
Service → validates JWT locally (no auth service call needed)
// Auth Service — generates JWT on login
@PostMapping("/auth/login")
public TokenResponse login(@RequestBody LoginRequest request) {
User user = userService.authenticate(request.getUsername(), request.getPassword());
    String token = Jwts.builder()
.setSubject(user.getId())
.claim("roles", user.getRoles())
.claim("email", user.getEmail())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
return new TokenResponse(token);
}
// Each microservice validates JWT locally
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    @Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
Claims claims = jwtUtil.extractClaims(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
claims.getSubject(),
null,
mapRolesToAuthorities(claims.get("roles", List.class))
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
// Service-level authorization
@RestController
@RequestMapping("/orders")
public class OrderController {
    @GetMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public Order getOrder(@PathVariable String id) { ... }
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteOrder(@PathVariable String id) { ... }
}

Service-to-service authentication: Use mTLS (mutual TLS) or OAuth 2.0 client credentials flow — services authenticate to each other, not just users.

Section 7: Observability

Q19. What is distributed tracing? How does it work with Micrometer and Zipkin?

When a user request spans 8 microservices, a standard stack trace is useless — the error is in one service, the symptom is in another. Distributed tracing tracks a request across all services with a correlation ID.

Key concepts:

  • Trace: The entire journey of one request across all services
  • Span: One operation within a trace (an HTTP call, a DB query, a Kafka consume)
  • Trace ID: Unique ID for the entire request — propagated in HTTP headers
  • Span ID: Unique ID for each individual operation
// Micrometer Tracing (Spring Boot 3+) — auto-instruments everything
// Just add the dependency and configure the exporter:
// build.gradle
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter'
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 100% sampling in dev, 0.1 in prod
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
// Trace ID automatically propagated in headers:
// X-B3-TraceId: abc123
// X-B3-SpanId: def456
// X-B3-ParentSpanId: ghi789
// Manual span creation for custom operations
@Service
public class OrderService {
private final Tracer tracer;
public Order processOrder(OrderRequest request) {
Span span = tracer.nextSpan().name("process-order").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
span.tag("orderId", request.getId());
return doProcessOrder(request);
} catch (Exception e) {
span.error(e);
throw e;
} finally {
span.end();
}
}
}

Q20. What are the three pillars of observability?

Every production microservices system needs all three:

1. Logs — What happened:

// Structured logging with trace correlation
log.info("Order processed successfully",
kv("orderId", order.getId()),
kv("amount", order.getAmount()),
kv("traceId", tracer.currentSpan().context().traceId())
);
// Output: {"orderId":"ORD-123","amount":99.99,"traceId":"abc123","level":"INFO"}

2. Metrics — How the system is performing:

// Micrometer metrics — auto-exported to Prometheus
@RestController
public class OrderController {
private final Counter orderCounter;
private final Timer orderTimer;
    public OrderController(MeterRegistry registry) {
orderCounter = registry.counter("orders.placed", "region", "us-east");
orderTimer = registry.timer("orders.processing.time");
}
@PostMapping("/orders")
public Order placeOrder(@RequestBody OrderRequest request) {
return orderTimer.record(() -> {
Order order = orderService.place(request);
orderCounter.increment();
return order;
});
}
}

3. Traces — How the request flowed: Distributed tracing as described above — Zipkin, Jaeger, or AWS X-Ray.

The observability stack most teams use:

  • Logs → ELK Stack (Elasticsearch, Logstash, Kibana) or Loki + Grafana
  • Metrics → Prometheus + Grafana
  • Traces → Jaeger or Zipkin
  • Alerting → Alertmanager or PagerDuty

Section 8: Deployment and Containers

Q21. What is Docker? How is it used in microservices?

Docker packages a microservice and all its dependencies into a container — an isolated, portable unit that runs identically in any environment. No more “works on my machine.”

# Dockerfile for a Spring Boot microservice
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app# Copy the fat jar
COPY target/order-service-1.0.jar app.jar
# Run as non-root user
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 8080# JVM tuning for containers
ENTRYPOINT ["java",
"-XX:MaxRAMPercentage=75.0",
"-XX:+UseG1GC",
"-Djava.security.egd=file:/dev/./urandom",
"-jar", "app.jar"]

Multi-stage build — smaller images:

# Build stage
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:resolve # cache dependencies layer
COPY src ./src
RUN mvn package -DskipTests
# Runtime stage — only JRE, no build tools
FROM eclipse-temurin:21-jre-alpine
COPY --from=build /app/target/order-service.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Q22. What is Kubernetes? What problems does it solve for microservices?

Kubernetes (K8s) is a container orchestration platform. It manages the lifecycle of containerized microservices at scale.

Problems Kubernetes solves:

# Deployment — declare desired state, K8s makes it reality
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3 # always run 3 instances
selector:
matchLabels:
app: order-service
template:
spec:
containers:
- name: order-service
image: myregistry/order-service:1.2.0
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe: # don't send traffic until ready
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
livenessProbe: # restart if unhealthy
httpGet:
path: /actuator/health/liveness
port: 8080
# Horizontal Pod Autoscaler — auto-scale based on CPU
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # scale when avg CPU > 70%

What Kubernetes provides:

  • Self-healing (restarts failed containers)
  • Auto-scaling (HPA, VPA, KEDA)
  • Rolling deployments (zero downtime)
  • Service discovery and load balancing
  • Configuration and secret management
  • Resource quotas and isolation
  • Health checks and readiness probes

Section 9: Design Principles and Patterns

Q23. What is the CAP theorem? How does it affect microservices design?

The CAP theorem states that a distributed system can provide at most two of three guarantees simultaneously:

  • C — Consistency: Every read receives the most recent write or an error
  • A — Availability: Every request receives a response (not necessarily the latest data)
  • P — Partition Tolerance: The system continues operating despite network partitions

Network partitions are inevitable in distributed systems. So the real choice is between CP and AP:

CP systems (Consistency + Partition Tolerance): During a partition, the system refuses to serve potentially stale data. Examples: HBase, ZooKeeper, etcd. Use for: financial transactions, inventory counts, anything where stale data causes real harm.

AP systems (Availability + Partition Tolerance): During a partition, the system serves potentially stale data. Examples: Cassandra, DynamoDB, CouchDB. Use for: user profiles, product catalogs, recommendations — where slight staleness is acceptable.

How this affects microservices design:

Most microservices systems favor AP — they embrace eventual consistency. The Order Service confirms an order immediately. The Inventory Service updates stock asynchronously. For a brief window, inventory counts may be slightly off. But orders keep flowing.

When strict consistency is required (bank transfers, stock trading), use sagas with compensation, distributed locks, or route those operations through a single service that owns the data.

Q24. What is the CQRS pattern? When would you use it?

CQRS (Command Query Responsibility Segregation) separates the write model (Commands) from the read model (Queries). Different models, different data stores, optimized for their specific purpose.

┌─────────────────┐
Command API ──→ │ Write Model │ ──→ [Command DB - normalized, consistent]
│ (Order, Pay) │ ↓ events
└─────────────────┘ ┌──────────────┐
│ Event Store │
┌─────────────────┐ └──────────────┘
Query API ──→ │ Read Model │ ←── [Read DB - denormalized, fast]
│ (Dashboard) │
└─────────────────┘
// Command side — handles writes
@CommandHandler
public class OrderCommandHandler {
public String handle(PlaceOrderCommand command) {
Order order = new Order(command);
orderRepository.save(order);
eventPublisher.publish(new OrderPlacedEvent(order));
return order.getId();
}
}
// Query side — optimized read model, pre-computed for queries
@QueryHandler
public class OrderQueryHandler {
// Read from a denormalized view — fast, no joins
public OrderDashboardView handle(GetOrderDashboardQuery query) {
return orderReadRepository.findDashboardView(query.getUserId());
}
}

When to use CQRS:

  • Read and write loads are very different in scale
  • Read queries are complex (multiple joins, aggregations)
  • Different consistency requirements for reads vs writes
  • Event sourcing (CQRS pairs naturally with it)

When NOT to use CQRS:

  • Simple CRUD applications — overkill
  • Small teams — operational overhead is not worth it
  • Tight consistency requirements — eventual consistency between write and read model is hard to manage

Q25. What is Event Sourcing?

Event Sourcing stores the history of state changes (events) rather than the current state. The current state is derived by replaying events.

// Instead of storing current order state:
// Order { id, status="SHIPPED", amount=99.99, ... }
// Store events:
// OrderPlaced { orderId, amount=99.99, timestamp }
// PaymentCharged { orderId, amount=99.99, timestamp }
// OrderShipped { orderId, trackingId, timestamp }
@Entity
public class OrderEvent {
private String orderId;
private String eventType;
private String payload; // JSON
private Instant occurredAt;
private long sequenceNumber;
}
// Reconstruct current state by replaying
public Order reconstruct(String orderId) {
List<OrderEvent> events = eventStore.getEvents(orderId);
Order order = new Order();
events.forEach(event -> order.apply(event)); // replay
return order;
}

Benefits:

  • Complete audit trail — every state change is recorded
  • Temporal queries — “what was the order state at 3pm yesterday?”
  • Event replay — rebuild read models, fix bugs by replaying with corrected logic
  • Natural fit with CQRS and microservices event-driven architecture

Challenges: Querying current state is indirect (rebuild from events or use a read model), schema evolution of past events is complex, storage grows over time (use snapshots for long-lived aggregates).

The Microservices Interview Checklist for 2026

Fundamentals

  • [ ] Microservices vs Monolith — advantages and disadvantages honestly
  • [ ] 12-Factor App — especially Config (III) and Disposability (IX)
  • [ ] When NOT to use microservices

Communication

  • [ ] REST vs gRPC — when to choose each
  • [ ] Synchronous vs asynchronous — temporal coupling
  • [ ] API Gateway responsibilities

Resilience

  • [ ] Circuit Breaker — three states, Resilience4j configuration
  • [ ] Bulkhead — thread pool vs semaphore
  • [ ] Saga pattern — choreography vs orchestration with compensation
  • [ ] Outbox pattern — guaranteed event delivery

Spring Cloud

  • [ ] Eureka — client-side vs server-side discovery
  • [ ] Feign client with fallback factory
  • [ ] Spring Cloud Config — refresh without restart

Kafka

  • [ ] Producer, consumer, topic, partition, consumer group
  • [ ] Idempotency — why it matters, how to implement
  • [ ] At-least-once vs exactly-once delivery

Security

  • [ ] JWT flow — auth service, gateway validation, service-level claims
  • [ ] Service-to-service auth — mTLS or client credentials

Observability

  • [ ] Three pillars — logs, metrics, traces
  • [ ] Distributed tracing — trace ID, span ID propagation
  • [ ] Health endpoints — liveness vs readiness

Deployment

  • [ ] Docker — multi-stage builds, JVM container flags
  • [ ] Kubernetes — Deployment, Service, HPA, probes

Architecture Patterns

  • [ ] CAP theorem — CP vs AP trade-off
  • [ ] CQRS — when it helps and when it hurts
  • [ ] Event Sourcing — benefits and challenges

Microservices are not a technology — they are an architecture style that brings real operational complexity in exchange for scalability and team autonomy. The engineers who succeed in microservices interviews are those who understand both sides of that trade-off and can speak to real patterns with production grounding.

📘 [Java Backend Interview Questions]

It covers 300+ questions across all experience levels, with detailed explanations, common follow-up questions, and the reasoning interviewers are actually looking for.

📘 [Complete Backend Interview Questions]

It covers 1000+ core Java, collections, concurrency, JVM internals, Spring, design patterns, and system design — with the kind of detailed explanations that don’t just help you pass interviews, but make you a better Java developer.imag

Which microservices pattern has saved you the most pain in production? Share it in the comments.

#Microservices #Java #SpringBoot #Kafka #Kubernetes #InterviewPrep #SoftwareArchitecture #SoftwareEngineering #Java2026 #SpringCloud

--

--