๐What You Will Build
Most microservices tutorials stop at "hello from service A calling service B." This one builds the pieces you actually ship: service discovery, an API gateway, declarative HTTP clients, fault tolerance, and containerization. We will build a small order system with three services, an order-service, an inventory-service, and a gateway, plus a Eureka registry that ties them together.
If you are new to the Spring ecosystem, first skim our Spring Boot course and the wider Microservices track. This tutorial assumes you already know how to write a basic REST controller.
๐The Architecture at a Glance
+------------------+ Client --> | API Gateway | (Spring Cloud Gateway, port 8080) +--------+---------+ | +----------------+----------------+ | |+---------------+ +------------------+| order-service | --Feign--> | inventory-service|| (port 8081) | | (port 8082) |+-------+-------+ +---------+--------+ | | +------------> Eureka <-------------+ (discovery, port 8761)Every service registers itself with Eureka. The gateway and the Feign clients look services up by name, not by hardcoded host and port. That single decision is what makes the system elastic, you can run three copies of inventory-service and traffic load-balances automatically.
๐Step 1: The Eureka Discovery Server
Create a new Spring Boot project with the spring-cloud-starter-netflix-eureka-server dependency. The server itself is almost no code, just an annotation.
@SpringBootApplication@EnableEurekaServerpublic class DiscoveryServerApplication { public static void main(String[] args) { SpringApplication.run(DiscoveryServerApplication.class, args); }}Its application.yml tells it not to register with itself:
server: port: 8761eureka: client: register-with-eureka: false fetch-registry: falsespring: application: name: discovery-serverStart it and visit http://localhost:8761, the Eureka dashboard is where every registered instance will appear.
๐Step 2: The Inventory Service
This is a normal Spring Boot service that also registers with Eureka. Add spring-boot-starter-web and spring-cloud-starter-netflix-eureka-client.
@RestController@RequestMapping("/api/inventory")public class InventoryController { @GetMapping("/{sku}") public InventoryResponse checkStock(@PathVariable String sku) { // In real life this hits a database int available = "SKU-123".equals(sku) ? 42 : 0; return new InventoryResponse(sku, available, available > 0); }}public record InventoryResponse(String sku, int quantity, boolean inStock) {}The magic is in application.yml, the spring.application.name is how other services will find it:
server: port: 8082spring: application: name: inventory-serviceeureka: client: service-url: defaultZone: http://localhost:8761/eurekaOnce running, refresh the Eureka dashboard, INVENTORY-SERVICE now shows up as UP.
๐Step 3: The Order Service with a Feign Client
The order-service needs stock data from inventory-service. Instead of hand-writing RestTemplate or WebClient calls, use OpenFeign, a declarative HTTP client. Add spring-cloud-starter-openfeign, enable it, and describe the remote API as a Java interface.
@SpringBootApplication@EnableFeignClientspublic class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); }}@FeignClient(name = "inventory-service") // resolved via Eureka, no URL!public interface InventoryClient { @GetMapping("/api/inventory/{sku}") InventoryResponse checkStock(@PathVariable("sku") String sku);}Notice there is no host or port, Feign asks Eureka where inventory-service lives and load-balances across every registered instance. Now the order logic reads like a local method call:
@RestController@RequestMapping("/api/orders")public class OrderController { private final InventoryClient inventoryClient; public OrderController(InventoryClient inventoryClient) { this.inventoryClient = inventoryClient; } @PostMapping public ResponseEntity<String> placeOrder(@RequestBody OrderRequest req) { InventoryResponse stock = inventoryClient.checkStock(req.sku()); if (!stock.inStock()) { return ResponseEntity.status(HttpStatus.CONFLICT).body("Out of stock"); } // persist order... return ResponseEntity.ok("Order placed for " + req.sku()); }}๐Step 4: Fault Tolerance with Resilience4j
What happens when inventory-service is slow or down? Without protection, threads pile up waiting and the failure cascades to order-service. Resilience4j adds a circuit breaker: after a threshold of failures it "opens" and fails fast, calling a fallback instead of hammering a dead service.
Add spring-cloud-starter-circuitbreaker-resilience4j, then annotate the call:
@Servicepublic class InventoryService { private final InventoryClient client; public InventoryService(InventoryClient client) { this.client = client; } @CircuitBreaker(name = "inventory", fallbackMethod = "fallback") @Retry(name = "inventory") public InventoryResponse check(String sku) { return client.checkStock(sku); } // Signature = original params + the thrown Throwable public InventoryResponse fallback(String sku, Throwable t) { return new InventoryResponse(sku, 0, false); // safe default }}Tune the breaker in application.yml:
resilience4j: circuitbreaker: instances: inventory: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10sNow if half of the last 10 calls fail, the breaker opens for 10 seconds and every request instantly returns the fallback, your order flow degrades gracefully instead of collapsing. Circuit breakers, bulkheads, and retries are core to real system design; we go deep on them in the High Level Design course.
๐Step 5: The API Gateway
Clients should not talk to five services on five ports. Spring Cloud Gateway gives you one front door with routing, auth, rate limiting, and CORS. Add spring-cloud-starter-gateway and configure routes that resolve through Eureka using the lb:// (load-balanced) scheme:
server: port: 8080spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true routes: - id: order-service uri: lb://order-service predicates: - Path=/api/orders/** - id: inventory-service uri: lb://inventory-service predicates: - Path=/api/inventory/**eureka: client: service-url: defaultZone: http://localhost:8761/eurekaNow POST http://localhost:8080/api/orders routes to the order service and GET http://localhost:8080/api/inventory/SKU-123 routes to inventory, all behind one port.
๐Step 6: Dockerize Everything
Each service gets a small, layered Dockerfile. Build a JAR first (mvn clean package), then:
FROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY target/order-service-0.0.1-SNAPSHOT.jar app.jarEXPOSE 8081ENTRYPOINT ["java", "-jar", "app.jar"]Then orchestrate the whole system with docker-compose.yml. The key change inside containers: services find Eureka by its service name, not localhost.
services: discovery: build: ./discovery-server ports: ["8761:8761"] inventory: build: ./inventory-service environment: - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://discovery:8761/eureka depends_on: [discovery] order: build: ./order-service environment: - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://discovery:8761/eureka depends_on: [discovery] gateway: build: ./gateway ports: ["8080:8080"] environment: - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://discovery:8761/eureka depends_on: [discovery]Run docker compose up --build and the entire stack, discovery, two business services, and the gateway, comes up together.
๐Production Checklist
Before you call this "production-ready," add these:
๐Where to Go Next
You now have the backbone of a real microservices system. The next skills are event-driven communication and designing for scale, both covered in the Microservices course and the System Design track. Also strengthen your Java foundation via the Java track so concurrency and memory never surprise you in production.
๐Learn It Live
Building this yourself is the fastest way to internalize it, but a mentor catching your mistakes in real time is faster. Prakalpana runs live online and 1-on-1 microservices training where you ship this exact system with review at every step. WhatsApp or call +91 9945619267 to book a free counselling session.