Microservices

By the end you'll be able to split a system into well-bounded Spring Boot services, choose REST or messaging for each call, wire them together with discovery and a gateway, and keep the whole thing standing up when one service fails.

Learn Microservices in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You should know REST APIs (HTTP endpoints), Deployment (Docker & JAR packaging), and Networking (HTTP clients). Comfort with JSON processing helps too, since services talk to each other in JSON.

A monolith is a Swiss Army knife — every tool in one body. Brilliant for camping, but you can't hand the scissors to one person and the blade to another, and replacing one tool means replacing the whole knife.

Microservices are a professional kitchen : separate stations for grill, pastry, and salads. Each station has its own ingredients (its own database), its own staff (its own team), and can be staffed up independently when it gets busy (independent scaling). A head chef calling out orders is the API gateway ; a notice board everyone reads is your message broker ; and a seating plan that always knows which station is open is service discovery .

💡 Key insight: microservices trade code complexity for operational complexity . Don't adopt them until your team can run a distributed system — networking, tracing, and eventual consistency all become your problem.

A microservice is a small, independently deployable program that owns one business capability end to end — its API and its data. The hardest part isn't the code; it's deciding where one service ends and the next begins. Draw the lines around things the business does (authenticate a user, place an order, charge a card), not around database tables.

Get a boundary wrong and two services will constantly need each other's data, and you'll be back to a monolith with network calls in the middle. The test of a good boundary: a service can change its internals, schema, and even its language without anyone else noticing.

In a cluster, instances come and go and their addresses change all the time — hard-coding host:port breaks constantly. Service discovery fixes this: each service registers itself with a registry (Spring Cloud's Eureka ) on startup, and callers ask the registry to resolve a name to a live address. Callers say "give me an order-service " instead of an IP.

The worked example below builds a tiny in-memory version of that registry so you can see the shape of the data Eureka keeps — a logical name mapped to the instances running behind it. Read it, then run it.

Services talk in two fundamentally different ways. Synchronous REST (or gRPC) is a phone call: you dial, you wait for the reply, and you're coupled to the other side being up right now. Use it when you need the answer to continue — "is this user authorised?".

Asynchronous messaging through a broker like Kafka or RabbitMQ is posting a notice on a board: you publish an event ("OrderPlaced") and walk away; anyone interested reacts in their own time. It decouples services, so a slow or down subscriber never blocks the producer, and you can add new subscribers later without touching the producer. The example models both with a tiny event bus.

You don't want browsers calling twenty internal services directly. An API gateway (Spring Cloud Gateway) is the single front door: it routes each incoming path to the right service, and centralises cross-cutting concerns — authentication, rate limiting, TLS, and CORS — so each service doesn't reimplement them.

A config server (Spring Cloud Config) solves the opposite end: instead of every service shipping its own copy of database URLs and feature flags, they fetch configuration from one central place (often a Git repo) at startup. Change a value once and every service can pick it up — no rebuild required.

In a distributed system, everything fails eventually — networks drop packets, services crash, databases time out. Resilience patterns make your system degrade gracefully instead of one failure cascading into all of them.

The most important is the circuit breaker . Like the breaker in your fuse box, it watches the failure rate and trips open once failures cross a threshold, so it stops calling a service that's clearly down and returns a fallback instead. After a cooldown it goes HALF_OPEN and tries a single request to see if the service recovered. A retry is the complementary tool: re-attempt a single call a few times to ride out a brief blip. The example builds a breaker by hand so the state machine is visible.

In a monolith, a failure gives you one stack trace. In microservices, a single user click might touch the gateway, then order-service , then payment-service , then a Kafka consumer — and a stack trace in any one of them tells you nothing about the others. Distributed tracing stitches the whole journey together.

The gateway tags the incoming request with a trace ID , and every service passes that ID along (usually as a header) so each hop becomes a span under the same trace. Tools like Micrometer Tracing with Zipkin collect those spans and draw a timeline of the full request, so you can see exactly which hop was slow or threw. Without it, debugging a distributed system is guesswork.

The support is gone now — only an outline remains. Build a tiny API gateway router that maps a request path to the service that owns it. The comments give the brief and the expected output; you write the logic.

You can now draw service boundaries around business capabilities, choose REST or messaging per call, wire services together with Eureka discovery, a Spring Cloud Gateway, and a config server, keep them resilient with Resilience4j circuit breakers and retries, trace a request across hops, and spot the distributed-monolith trap before you fall into it.

Next up: CLI Tools — building professional command-line applications with Java and picocli.

Practice quiz

How should you draw microservice boundaries?

  • Around database tables
  • Around programming languages
  • Around business capabilities (what the business does)
  • One service per developer

Answer: Around business capabilities (what the business does). Draw boundaries around business capabilities (authenticate, place an order, charge a card), not around tables.

When should you choose synchronous REST over messaging?

  • When the caller needs the answer to continue right now
  • When announcing that something happened
  • When you want loose coupling
  • When the callee might be down

Answer: When the caller needs the answer to continue right now. Use REST/gRPC when you need the reply to proceed (e.g. 'is this user authorised?'); use messaging to announce events.

What problem does service discovery (Eureka) solve?

  • Encrypting traffic
  • Storing logs
  • Running database migrations
  • Resolving a service name to a live address so callers never hard-code host:port

Answer: Resolving a service name to a live address so callers never hard-code host:port. Instances come and go; services register with Eureka and callers resolve a name to a live instance instead of an IP.

What does an API gateway do?

  • Stores each service's data
  • Acts as the single front door — routing plus auth, rate limiting, TLS
  • Resolves service names
  • Sends async events

Answer: Acts as the single front door — routing plus auth, rate limiting, TLS. The gateway routes incoming paths to services and centralises cross-cutting concerns like auth and rate limiting.

How does a circuit breaker behave once failures cross its threshold?

  • It trips OPEN and returns a fallback instead of calling the failing service
  • It retries forever
  • It restarts the service
  • It logs and continues calling

Answer: It trips OPEN and returns a fallback instead of calling the failing service. On too many failures the breaker trips open, failing fast with a fallback so it stops hammering a service that is down.

What is the difference between a retry and a circuit breaker?

  • They are the same
  • Retry is slower
  • Retry re-attempts a single call for brief blips; a breaker stops calls when a service is clearly down
  • A breaker retries automatically

Answer: Retry re-attempts a single call for brief blips; a breaker stops calls when a service is clearly down. Retry handles transient flakes; the breaker watches the failure rate and trips open to protect a downed service.

Why does each microservice own its own database?

  • To save money
  • A shared database secretly couples services, so a schema change can break another
  • Databases are faster that way
  • To avoid writing SQL

Answer: A shared database secretly couples services, so a schema change can break another. Database-per-service keeps ownership clear; a shared DB couples services and breaks independent deployment.

What is a 'distributed monolith'?

  • A well-designed microservice system
  • A single large application
  • A type of message broker
  • Services split apart but so tightly coupled they must deploy together and share a database

Answer: Services split apart but so tightly coupled they must deploy together and share a database. It is the worst of both worlds: a monolith's rigidity plus a distributed system's latency and failure modes.

What does distributed tracing give you?

  • Faster databases
  • A trace ID passed across services so you can follow one request across hops
  • Automatic retries
  • Encrypted logs

Answer: A trace ID passed across services so you can follow one request across hops. A trace ID flows with the request so each hop is a span; tools like Zipkin draw the full request timeline.

Which messaging brokers are typical for asynchronous events?

  • MySQL and Postgres
  • Eureka and Consul
  • Kafka and RabbitMQ
  • Zipkin and Micrometer

Answer: Kafka and RabbitMQ. Kafka and RabbitMQ are message brokers; a producer publishes an event and subscribers react in their own time.