Microservices Order System in Spring Boot
Build a microservices order system with service discovery and resilience.
Introduction
30 Second Summary
Every time you place an order online, dozens of services work together behind the scenes. One checks your payment, another verifies stock, another handles shipping. If any one of those services goes down, the whole system shouldn't collapse.
In this project, you will build a microservices order processing system where services discover each other dynamically and communicate reliably. You will also add resilience patterns so the system degrades gracefully instead of crashing when a service becomes unavailable.
What You'll Build
A multi-service order processing system running on your local machine, where an Order Service dynamically discovers an Inventory Service through a service registry and checks stock availability before confirming orders.
By the end of this project, you'll have:
- A Eureka Discovery Server with a web dashboard showing all registered services.
- Two independent Spring Boot microservices (Order Service and Inventory Service) communicating via service discovery.
- Inter-service communication using OpenFeign with load-balanced service discovery and no hardcoded URLs.
- Secret Mission: Add a Resilience4j circuit breaker so the Order Service degrades gracefully when the Inventory Service goes down.
Are there any prerequisites?
This project assumes basic Java knowledge (object-oriented programming, collections) and familiarity with REST APIs. You do not need prior experience with Spring Boot or microservices.
Before We Start
Spring Boot 3.5.15 requires JDK 21 to compile and run. Your machine likely has an older Java version installed, so the first thing to do is upgrade your Java runtime.
In this step, you'll install JDK 21, add two VS Code extension packs that enable Java development, and confirm everything works together in your editor's terminal.
In this step, get ready to:
- Install JDK 21 (Eclipse Temurin) via Homebrew.
- Add the Extension Pack for Java and Spring Boot Extension Pack to VS Code.
- Verify your terminal detects JDK 21.
Install JDK 21 via Homebrew
Eclipse Temurin is a free, production-ready distribution of OpenJDK that installs cleanly on macOS. It places itself in the standard Java directory so macOS tools can detect it automatically.
- Check which Java version your system currently has by running this command:
java -version
✔️ I see version 21 or higher
Your JDK is ready. Skip ahead to the next substep.
ⓧ I see an older version
Your system has an older Java version (like JDK 8 or 11). Install JDK 21 alongside it without removing the old one.
- Install Eclipse Temurin JDK 21 by running this command:
brew install --cask temurin@21
What does this install?
This installs JDK 21 to /Library/Java/JavaVirtualMachines/temurin-21.jdk/. The cask install method places it in the standard macOS Java directory so system tools detect it automatically.
macOS may still default to your older JDK. You need to tell your shell to use JDK 21 by setting the JAVA_HOME environment variable.
- Add the JDK 21 path to your shell profile by running these commands:
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 21)' >> ~/.zprofile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.zprofile
source ~/.zprofile
What do these lines do?
The first line sets JAVA_HOME to wherever macOS installed JDK 21. The second line puts that JDK's binaries first in your PATH so it takes priority over older versions.
The source command reloads your profile so the changes take effect immediately without restarting the terminal.
- Confirm JDK 21 is now active by running:
java -version
You should see output starting with openjdk version "21 followed by a build number.
Still seeing your old Java version?
Close your terminal completely and open a new one, then run source ~/.zprofile again. The JAVA_HOME change only applies to new shell sessions.
If it still shows the old version, verify the install landed correctly by running /usr/libexec/java_home -v 21. This should print the path to the Temurin JDK.
help me fix my JAVA_HOME so java -version shows 21
ⓧ Command not found
Java is not installed on your machine yet. You'll install JDK 21 from scratch.
- Install Eclipse Temurin JDK 21 by running this command:
brew install --cask temurin@21
Seeing command not found: brew?
Homebrew is not installed on your Mac. Install it first by following the instructions at https://brew.sh. After installing Homebrew, close and reopen your terminal, then try the brew install command again.
help me install Homebrew on my Mac
Now tell your shell where to find JDK 21 by setting the JAVA_HOME environment variable.
- Add the JDK 21 path to your shell profile by running these commands:
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 21)' >> ~/.zprofile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.zprofile
source ~/.zprofile
What do these lines do?
The first line sets JAVA_HOME to wherever macOS installed JDK 21. The second line puts that JDK's binaries first in your PATH so the java command resolves to version 21.
The source command reloads your profile so the changes take effect immediately.
- Confirm JDK 21 is now active by running:
java -version
You should see output starting with openjdk version "21 followed by a build number.
Still not seeing version 21?
Close your terminal completely and open a new one, then run source ~/.zprofile again. Changes to ~/.zprofile only apply to new shell sessions.
help me get java -version to show 21 after installing Temurin
Install VS Code extensions
Two extension packs turn VS Code into a full Java IDE. One handles language support, debugging, and Maven integration. The other adds Spring Boot-specific tooling like a project dashboard and initializer.
- In VS Code, press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions sidebar.
- Type Extension Pack for Java in the search bar.
- Find the result by Microsoft and click Install.
- Clear the search bar and type Spring Boot Extension Pack.
- Find the result by VMware and click Install.
What do these extensions provide?
The Extension Pack for Java bundles language support, IntelliSense, debugging, and Maven/Gradle project management. It handles everything from code completion to running tests.
The Spring Boot Extension Pack adds Spring-specific features: a Boot Dashboard for managing running services, Spring Initializr integration for generating projects, and live application information.
Extensions not appearing in search?
Make sure you are searching in the Marketplace tab (not the installed tab). If results seem empty, check your internet connection and try reloading VS Code.
help me find and install Java extensions in VS Code
Verify Java works in VS Code
The final check is confirming VS Code's integrated terminal picks up JDK 21. This matters because Spring Boot projects compile and run from inside this terminal.
- Open VS Code's integrated terminal by pressing Ctrl+` (backtick key, located above Tab on most keyboards).
- Confirm JDK 21 is detected by running this command:
java -version
You should see output starting with openjdk version "21 and mentioning Temurin in the build name.
What about Maven?
You do not need to install Maven separately. Every Spring Boot project generated from Spring Initializr includes a Maven Wrapper (mvnw) that downloads the correct Maven version automatically when you first run it.
VS Code terminal showing a different Java version?
VS Code may have cached an older shell session. Close the terminal panel, then reopen it with Ctrl+`. If the version still looks wrong, fully quit VS Code and reopen it so it picks up the updated ~/.zprofile.
help me get VS Code terminal to use JDK 21
Your development environment is ready. Next up, you'll create the Eureka Discovery Server that acts as the central registry for all your microservices.
Set Up Your Development Environment
Building microservices means running multiple Java applications simultaneously. A properly configured development environment ensures your tools detect the right JDK, your editor understands Spring Boot code, and your build tool resolves dependencies automatically.
In this step, you'll finalize your JDK 21 PATH configuration, confirm your VS Code extensions are active, and learn why Maven requires zero setup for Spring Boot projects.
In this step, get ready to:
- Add JDK 21 to your shell PATH for reliable toolchain access.
- Confirm your Java and Spring Boot VS Code extensions are active.
- Understand how Maven Wrapper eliminates a separate Maven install.
Add JDK 21 to your shell PATH
Your JDK 21 installation is already detected by macOS through its built-in Java shim. Adding the JDK's bin directory to your PATH explicitly ensures every Java tool (javac, jshell, the Maven Wrapper) resolves directly without relying on the shim.
- Open your ~/.zprofile file in a text editor by running this command in your VS Code terminal:
open -e ~/.zprofile
- Find the line that exports JAVA_HOME. Add the following line directly below it:
export PATH=$JAVA_HOME/bin:$PATH
What does this line do?
This prepends the JDK 21 bin directory to your system PATH. Any command that lives inside the JDK (like javac or jar) is now available directly in every new terminal session.
- Save and close the file.
- Reload your shell configuration by running:
source ~/.zprofile
- Verify your JDK 21 is fully configured by running:
java -version
You should see output starting with openjdk version "21.0.11" confirming Eclipse Temurin JDK 21 is active.
Still seeing an older Java version?
If java -version shows a version lower than 21, your shell may be picking up a different JDK. Run /usr/libexec/java_home -V to list all installed JDKs and confirm that Temurin 21 appears.
Then verify your ~/.zprofile contains both the JAVA_HOME and PATH exports. Make sure there is no other export below them that overrides the value.
Why is my java -version still showing an old JDK after updating my PATH?
Confirm your VS Code extensions are active
Two extension packs give VS Code full Java and Spring Boot support. Confirming they are active now prevents confusing errors later when you generate and run Spring Boot projects.
- Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
- Type Extension Pack for Java in the search bar.
- Confirm the extension by Microsoft shows a blue Disable button (meaning it is enabled and active).
- Clear the search bar and type Spring Boot Extension Pack.
- Confirm the extension by VMware also shows the blue Disable button.
What do these extensions do?
The Extension Pack for Java bundles language support, debugging, test running, and Maven/Gradle tooling into one install. It gives VS Code full IDE-level Java capabilities.
The Spring Boot Extension Pack adds Spring-specific features like the Spring Boot Dashboard, application property auto-complete, and the Spring Initializr integration you will use in the next step.
Extension shows Install instead of Disable?
If you see an Install button instead of Disable, click it to install the extension. VS Code may prompt you to reload the window afterwards.
My VS Code Java extensions are not detecting my JDK 21 installation. How do I fix this?
Verify your Java toolchain
Spring Boot projects ship with a built-in Maven Wrapper (mvnw). This means you do not need to install Maven separately.
What is the Maven Wrapper?
When you generate a project from Spring Initializr, it includes a mvnw script (mvnw.cmd on Windows). This script downloads and uses the exact Maven version the project needs, automatically.
You will run ./mvnw spring-boot:run to start each microservice later in this project. The wrapper handles everything.
- Confirm your VS Code terminal detects JDK 21 by running:
java -version
You should see the same Temurin 21 output as before. This confirms VS Code's integrated terminal inherits your shell configuration correctly.
No Maven install needed
If you have used Java projects before, you might expect to run mvn --version here. With Spring Boot projects, the Maven Wrapper replaces a global Maven installation entirely. You will see mvnw in action starting in the next step.
Your development environment is locked in. Next up, you will create the Eureka Discovery Server that acts as the central registry for all your microservices.
Create the Eureka Discovery Server
Your development environment is ready with JDK 21 and a fully configured VS Code workspace. Now it's time to build the backbone of your microservices system.
In a microservices architecture, services need a way to find each other without hardcoding IP addresses or ports. A discovery server solves this problem by acting as a phone book that every service registers with. In this step, you'll create a Eureka Discovery Server that other services will use to locate each other dynamically.
In this step, get ready to:
- Generate a Spring Boot project from Spring Initializr with the Eureka Server dependency.
- Configure the server to run as a standalone discovery registry.
- Launch the Eureka Dashboard and verify it's running.
Generate the project using Spring Initializr
Spring Initializr is a web tool that generates pre-configured Spring Boot projects. Instead of manually creating folder structures and configuring dependencies, you pick what you need and download a ready-to-go project.
- Open https://start.spring.io in your browser.
- Under Project, select Maven.
- Under Language, select Java.
- Under Spring Boot, select 3.5.15.
- In the Project Metadata section, set Group to com.ordersystem.
- Set Artifact to discovery-server.
- Under Java, select 21.
- Click ADD DEPENDENCIES on the right side.
- Search for Eureka Server and select it.
- Click GENERATE to download the project zip file.
What is Eureka?
Eureka is a service discovery server originally built by Netflix and now maintained as part of Spring Cloud. It keeps a live registry of which services are running and where.
When a microservice starts up, it registers its name and address with Eureka. Other services can then look up that name to find the address, instead of needing it hardcoded.
- Find the downloaded discovery-server.zip file in your Downloads folder and extract it.
- Move the extracted discovery-server folder to your Desktop.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the discovery-server folder on your Desktop, then click Open.
Trust the workspace
VS Code may ask if you trust the authors of this folder. Click Yes, I trust the authors to enable full Java language support.
VS Code loads the project and the Java extension begins indexing. You should see the project structure in the Explorer sidebar with folders like src, pom.xml, and mvnw.
Configure the Eureka Server
Spring Initializr gave you the project skeleton, but you still need to tell Spring Boot to activate Eureka Server mode. This requires an annotation on the main class and a YAML configuration file.
- In the Explorer sidebar, navigate to src/main/java/com/ordersystem/discoveryserver/.
- Open DiscoveryServerApplication.java.
- Replace the entire file content with the following code:
package com.ordersystem.discoveryserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// Enables this application to act as a Eureka discovery server
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
What does this code do?
- @SpringBootApplication marks this class as the entry point for a Spring Boot application.
- @EnableEurekaServer activates the embedded Eureka server inside this application. Without this annotation, the application would just be a regular Spring Boot app.
- Save DiscoveryServerApplication.java with Cmd+S (macOS) or Ctrl+S (Windows).
Next, you need a configuration file that sets the server port and tells Eureka not to register itself as a client. Spring Initializr generates an empty application.properties file by default, but you'll use YAML format for better readability.
- In the Explorer sidebar, navigate to src/main/resources/.
- Right-click application.properties and select Rename.
- Rename it to application.yml and press Enter.
- Open application.yml and paste the following configuration:
server:
port: 8761
spring:
application:
name: discovery-server
eureka:
client:
register-with-eureka: false
fetch-registry: false
What does this configuration do?
- server.port: 8761 sets the port that the Eureka Dashboard and API will run on. Port 8761 is the conventional default for Eureka.
- spring.application.name gives this service a human-readable name in logs and the dashboard.
- register-with-eureka: false tells this server not to register itself as a client. It is the registry, not a service that needs to be discovered.
- fetch-registry: false disables fetching the service list from another Eureka server. This is a standalone instance, not a cluster.
- Save application.yml with Cmd+S (macOS) or Ctrl+S (Windows).
Seeing red underlines in the Java file?
The Java extension may take a moment to download dependencies and resolve imports. Wait 30-60 seconds for the indexing to complete. The red underlines should disappear once the project is fully loaded.
help me fix red underline errors in my Spring Boot project in VS Code
✔️ Awesome, I've got everything!
Great. Double check you've saved both files before moving on.
ⓧ I'd like to double check the full code
Here's what your two files should look like:
package com.ordersystem.discoveryserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
server:
port: 8761
spring:
application:
name: discovery-server
eureka:
client:
register-with-eureka: false
fetch-registry: false
Run and verify the Eureka Dashboard
Your Discovery Server is configured. Time to start it and confirm the Eureka Dashboard loads.
- Open the integrated terminal in VS Code with Ctrl+` (backtick key, same on macOS and Windows).
- Make the Maven Wrapper executable and start the server by running these commands:
chmod +x mvnw
./mvnw spring-boot:run
What does this command do?
chmod +x mvnw grants execute permission to the Maven Wrapper script. The wrapper downloads the correct Maven version automatically, so you never need to install Maven separately.
./mvnw spring-boot:run compiles your project and starts the Spring Boot application. The first run downloads all dependencies, which may take 1-2 minutes.
Wait for the terminal output to show a line like Started DiscoveryServerApplication. This confirms the server is running.
- Open your browser and navigate to http://localhost:8761.
You should see the Eureka Dashboard with the heading Instances currently registered with Eureka showing an empty list. This is expected because no services have registered yet.
Server not starting?
- If you see a permission denied error, make sure you ran chmod +x mvnw first.
- If the terminal says port 8761 is already in use, another process is occupying that port. Close other Java applications or restart your machine.
- If dependencies fail to download, check your internet connection and try the command again.
help me fix my Eureka Discovery Server startup error
Your service registry is live. Next up, you'll build the Inventory Service and watch it appear in this dashboard when it registers with Eureka.
Build the Inventory Service
Your Discovery Server is running and ready to accept service registrations. The next piece of the puzzle is building a service that actually registers itself.
In this step, you'll create the Inventory Service. This is a standalone Spring Boot application that manages product stock data and announces itself to Eureka on startup.
In this step, get ready to:
- Generate the Inventory Service project from Spring Initializr.
- Create a REST API with endpoints for checking product stock.
- Verify the service registers with Eureka automatically.
Generate and configure the project
Just like the Discovery Server, you'll generate this project using Spring Initializr. This time the dependencies are different because the Inventory Service is a web API that registers as a client with Eureka.
- Open https://start.spring.io in your browser.
- Set the project type to Maven and language to Java.
- Set the Spring Boot version to 3.5.15.
- Under Project Metadata, set Group to com.ordersystem and Artifact to inventory-service.
- Click ADD DEPENDENCIES and add Spring Web and Eureka Discovery Client.
- Click GENERATE to download the zip file.
- Extract the zip file into the same workspace folder where your discovery-server project lives.
- Open the inventory-service folder in a new VS Code window by clicking File, then New Window, then File > Open Folder, and selecting the inventory-service folder.
Why a new VS Code window?
Each microservice is its own independent project with its own build files and dependencies. Opening each service in its own VS Code window keeps things clean and lets the Java extension load the correct project context.
Spring Initializr generates an application.properties file by default. You'll replace it with an application.yml file for better readability with nested configuration.
- In the VS Code Explorer sidebar, navigate to src/main/resources.
- Delete the application.properties file.
- Create a new file called application.yml in the same src/main/resources folder.
- Add the following configuration to application.yml:
server:
port: 8082
spring:
application:
name: inventory-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
What does this configuration do?
- server.port: 8082 tells Spring Boot to run this service on port 8082 instead of the default 8080.
- spring.application.name: inventory-service gives this service a name that Eureka uses to identify it in the registry.
- eureka.client.service-url.defaultZone points to your running Discovery Server so this service knows where to register.
- Save application.yml.
YAML not being recognized?
Make sure the file is named exactly application.yml (not application.yaml or with extra spaces). Also confirm it lives directly inside src/main/resources/, not in a subfolder.
help me troubleshoot my application.yml file
✔️ Awesome, I've got everything!
Great. Double check you've saved your file and move on to building the API.
ⓧ I'd like to double check the full code
server:
port: 8082
spring:
application:
name: inventory-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Create the Inventory REST API
The Inventory Service needs two things: a data model representing a product and a controller that serves product data over HTTP. You'll use a Java record for the model and Spring's @RestController for the API.
- In the VS Code Explorer, navigate to src/main/java/com/ordersystem/inventoryservice/.
- Create a new folder called model inside inventoryservice.
- Inside the model folder, create a new file called Product.java.
- Add the following code to Product.java:
package com.ordersystem.inventoryservice.model;
public record Product(Long id, String name, int quantity, double price) {
}
- Save Product.java.
- Now create a new folder called controller inside inventoryservice (at the same level as model).
- Inside the controller folder, create a new file called InventoryController.java.
- Add the following code to InventoryController.java:
package com.ordersystem.inventoryservice.controller;
import com.ordersystem.inventoryservice.model.Product;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
private final Map<Long, Product> inventory = new HashMap<>();
public InventoryController() {
// Pre-populate with sample products
inventory.put(1L, new Product(1L, "Laptop", 50, 999.99));
inventory.put(2L, new Product(2L, "Headphones", 200, 79.99));
inventory.put(3L, new Product(3L, "Keyboard", 150, 49.99));
}
@GetMapping
public List<Product> getAllProducts() {
// Return all products in the inventory
return List.copyOf(inventory.values());
}
@GetMapping("/{productId}")
public ResponseEntity<Product> getProduct(@PathVariable Long productId) {
// Look up a single product by ID
Product product = inventory.get(productId);
if (product == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(product);
}
}
What does this code do?
The Product record is a compact Java class that holds four fields: id, name, quantity, and price. Java records automatically generate constructors, getters, and toString() for you.
The InventoryController stores products in a HashMap (an in-memory data store). It exposes two endpoints: GET /api/inventory returns all products, and GET /api/inventory/{productId} returns a single product or a 404 if not found.
- Save InventoryController.java.
Seeing red squiggly lines in VS Code?
The Java extension may take a moment to index the project and resolve imports. Wait 10-15 seconds after saving for the errors to disappear. If they persist, check that your Product.java package declaration matches com.ordersystem.inventoryservice.model and that the file lives in the correct folder path.
help me fix import errors in my InventoryController
✔️ Awesome, I've got everything!
Great. Double check you've saved both files before moving on.
ⓧ I'd like to double check the full code
package com.ordersystem.inventoryservice.model;
public record Product(Long id, String name, int quantity, double price) {
}
package com.ordersystem.inventoryservice.controller;
import com.ordersystem.inventoryservice.model.Product;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
private final Map<Long, Product> inventory = new HashMap<>();
public InventoryController() {
inventory.put(1L, new Product(1L, "Laptop", 50, 999.99));
inventory.put(2L, new Product(2L, "Headphones", 200, 79.99));
inventory.put(3L, new Product(3L, "Keyboard", 150, 49.99));
}
@GetMapping
public List<Product> getAllProducts() {
return List.copyOf(inventory.values());
}
@GetMapping("/{productId}")
public ResponseEntity<Product> getProduct(@PathVariable Long productId) {
Product product = inventory.get(productId);
if (product == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(product);
}
}
Run and verify registration
Time to start the Inventory Service and confirm it registers with your Discovery Server. Make sure your Discovery Server is still running on port 8761 before proceeding.
- Open a terminal in VS Code (press Ctrl+` or go to Terminal > New Terminal).
- Start the Inventory Service by running this command:
./mvnw spring-boot:run
The first run downloads dependencies, so it may take a minute. You should see log output ending with a line showing the application started on port 8082.
- Open http://localhost:8761 in your browser to check the Eureka Dashboard.
You should now see INVENTORY-SERVICE listed under Instances currently registered with Eureka. This confirms your service discovered the registry and announced itself automatically.
- Test the API directly by running this command in a new terminal tab:
curl http://localhost:8082/api/inventory/1
You should see a JSON response with the Laptop product details, confirming the REST API is working.
Don't see INVENTORY-SERVICE in Eureka?
Eureka registration can take up to 30 seconds. Refresh the dashboard a few times. If the service still doesn't appear, check that your application.yml has the correct defaultZone value pointing to http://localhost:8761/eureka/ and that the Discovery Server terminal is still running.
help me debug why my service isn't registering with Eureka
Your first microservice is live and registered. Next up, you'll build the Order Service that discovers and calls this Inventory Service through Eureka instead of using hardcoded URLs.
Build the Order Service with Service Discovery
Your Inventory Service is running and registered with Eureka. Now you need a second microservice that can find it and communicate with it.
The Order Service will look up the Inventory Service by name through Eureka, check stock availability, and confirm or reject orders. You will use OpenFeign to make inter-service calls feel like calling a local method.
In this step, get ready to:
- Generate the Order Service project from Spring Initializr with service discovery dependencies.
- Create a Feign client that discovers Inventory Service by name through Eureka.
- Test the full inter-service communication flow with a real order request.
Generate and configure the project
Just like the Inventory Service, you will scaffold the Order Service using Spring Initializr. This time you will add an extra dependency for declarative HTTP communication.
- Open https://start.spring.io in your browser.
- Select Maven as the project type.
- Select Java as the language.
- Set the Spring Boot version to 3.5.15.
- Set Group to com.ordersystem.
- Set Artifact to order-service.
- Set Java version to 21.
- Click ADD DEPENDENCIES and search for each of these, adding them one at a time: Spring Web, Eureka Discovery Client, OpenFeign.
- Click Generate to download the ZIP file.
- Extract the order-service folder from the ZIP into the same workspace folder where your discovery-server and inventory-service folders live.
- Open the order-service folder in VS Code.
What did Spring Initializr generate?
The generated project includes a Maven Wrapper (mvnw) so you do not need Maven installed separately. It also includes a pom.xml with the Spring Cloud BOM and all three dependencies pre-configured.
Spring Initializr generates an application.properties file by default. You will replace it with an application.yml file for cleaner nested configuration.
- Delete the generated src/main/resources/application.properties file.
- Create a new file at src/main/resources/application.yml and add the following configuration:
server:
port: 8081
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
What does this configuration do?
- Port 8081 keeps the Order Service separate from the Discovery Server (8761) and Inventory Service (8082).
- The application name order-service is the identity this service registers under in Eureka.
- The defaultZone tells the service where to find the Eureka registry, the same URL your Inventory Service uses.
- Save application.yml.
✔️ Awesome, I've got everything!
Great. Make sure you have saved application.yml and deleted the old application.properties file.
ⓧ I'd like to double check the full code
server:
port: 8081
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Create the Feign client and Order API
With the project configured, you will now write the application code. The Order Service needs a way to call the Inventory Service without hardcoding its URL.
Why OpenFeign instead of RestTemplate?
OpenFeign lets you declare an HTTP client as a plain Java interface. You annotate it with the target service name and Spring Cloud handles discovery, load balancing, and request mapping automatically.
The result is inter-service calls that look like calling a local method. No manual URL construction, no boilerplate HTTP client setup.
First, enable Feign in the main application class.
- Open src/main/java/com/ordersystem/orderservice/OrderServiceApplication.java and replace its contents with the following:
package com.ordersystem.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
What does this code do?
- The @EnableFeignClients annotation tells Spring to scan for interfaces annotated with @FeignClient and create implementations automatically at runtime.
- Without this annotation, Spring would not know to wire up your declarative HTTP clients.
- Save OrderServiceApplication.java.
Seeing import errors?
If VS Code shows red underlines on the EnableFeignClients import, wait a few seconds for the Java extension to finish indexing the project dependencies. You can also try running ./mvnw compile in the terminal to force dependency resolution.
Help me fix import errors in my Spring Boot project
✔️ Awesome, I've got everything!
Great. Make sure OrderServiceApplication.java is saved.
ⓧ I'd like to double check the full code
package com.ordersystem.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
Next, create the data model classes that represent products, order requests, and order responses.
- Create a new package folder at src/main/java/com/ordersystem/orderservice/model/.
- Inside the model folder, create three files with the following code:
package com.ordersystem.orderservice.model;
public record Product(Long id, String name, int quantity, double price) {
}
package com.ordersystem.orderservice.model;
public record OrderRequest(Long productId, int quantity) {
}
package com.ordersystem.orderservice.model;
public record OrderResponse(String status, String message, Long productId, int quantity, double totalPrice) {
}
What do these records do?
- The Product record mirrors the shape returned by the Inventory Service. Spring uses it to deserialize the JSON response automatically.
- The OrderRequest record defines what the client sends when placing an order (which product and how many).
- The OrderResponse record defines what the Order Service returns, including the status (CONFIRMED or FAILED) and total price.
- Save all three model files.
✔️ Awesome, I've got everything!
Great. Confirm all three files exist in src/main/java/com/ordersystem/orderservice/model/.
ⓧ I'd like to double check the full code
package com.ordersystem.orderservice.model;
public record Product(Long id, String name, int quantity, double price) {
}
package com.ordersystem.orderservice.model;
public record OrderRequest(Long productId, int quantity) {
}
package com.ordersystem.orderservice.model;
public record OrderResponse(String status, String message, Long productId, int quantity, double totalPrice) {
}
Now create the Feign client interface that declares how to call the Inventory Service.
- Create a new package folder at src/main/java/com/ordersystem/orderservice/client/.
- Inside the client folder, create InventoryClient.java with the following code:
package com.ordersystem.orderservice.client;
import com.ordersystem.orderservice.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// Declares a client for the service registered as "inventory-service" in Eureka
@FeignClient(name = "inventory-service")
public interface InventoryClient {
// Maps to GET /api/inventory/{productId} on the Inventory Service
@GetMapping("/api/inventory/{productId}")
Product getProduct(@PathVariable("productId") Long productId);
}
What does this code do?
- The @FeignClient(name = "inventory-service") annotation tells Spring Cloud to look up a service called inventory-service in the Eureka registry. No hardcoded URL needed.
- The @GetMapping annotation maps the method to the same endpoint your Inventory Service exposes. Spring generates the HTTP call at runtime.
- If multiple instances of inventory-service were running, Spring Cloud would automatically load-balance requests across them.
- Save InventoryClient.java.
✔️ Awesome, I've got everything!
Great. Make sure InventoryClient.java is saved in the client package.
ⓧ I'd like to double check the full code
package com.ordersystem.orderservice.client;
import com.ordersystem.orderservice.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "inventory-service")
public interface InventoryClient {
@GetMapping("/api/inventory/{productId}")
Product getProduct(@PathVariable("productId") Long productId);
}
Finally, create the Order controller that ties everything together.
- Create a new package folder at src/main/java/com/ordersystem/orderservice/controller/.
- Inside the controller folder, create OrderController.java with the following code:
package com.ordersystem.orderservice.controller;
import com.ordersystem.orderservice.client.InventoryClient;
import com.ordersystem.orderservice.model.OrderRequest;
import com.ordersystem.orderservice.model.OrderResponse;
import com.ordersystem.orderservice.model.Product;
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("/api/orders")
public class OrderController {
private final InventoryClient inventoryClient;
// Spring injects the Feign client implementation automatically
public OrderController(InventoryClient inventoryClient) {
this.inventoryClient = inventoryClient;
}
@PostMapping
public OrderResponse placeOrder(@RequestBody OrderRequest request) {
// Call Inventory Service through Eureka (no hardcoded URL)
Product product = inventoryClient.getProduct(request.productId());
if (product == null) {
return new OrderResponse("FAILED", "Product not found", request.productId(), request.quantity(), 0);
}
if (product.quantity() < request.quantity()) {
return new OrderResponse("FAILED", "Insufficient stock. Available: " + product.quantity(), request.productId(), request.quantity(), 0);
}
double totalPrice = product.price() * request.quantity();
return new OrderResponse("CONFIRMED", "Order placed successfully for " + product.name(), request.productId(), request.quantity(), totalPrice);
}
}
What does this code do?
- The placeOrder method receives a JSON body with a product ID and quantity.
- It calls inventoryClient.getProduct() which triggers an HTTP GET to the Inventory Service through Eureka. The URL is resolved dynamically from the registry.
- If the product exists and has enough stock, it returns a CONFIRMED response with the total price. Otherwise it returns FAILED with an explanation.
- Save OrderController.java.
Seeing compilation errors?
Make sure your Product.java, OrderRequest.java, and OrderResponse.java files are in the model package and that InventoryClient.java is in the client package. The import paths in OrderController.java must match your file locations exactly.
Help me fix compilation errors in my Order Service
✔️ Awesome, I've got everything!
Great. Double check that OrderController.java is saved in the controller package.
ⓧ I'd like to double check the full code
package com.ordersystem.orderservice.controller;
import com.ordersystem.orderservice.client.InventoryClient;
import com.ordersystem.orderservice.model.OrderRequest;
import com.ordersystem.orderservice.model.OrderResponse;
import com.ordersystem.orderservice.model.Product;
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("/api/orders")
public class OrderController {
private final InventoryClient inventoryClient;
public OrderController(InventoryClient inventoryClient) {
this.inventoryClient = inventoryClient;
}
@PostMapping
public OrderResponse placeOrder(@RequestBody OrderRequest request) {
Product product = inventoryClient.getProduct(request.productId());
if (product == null) {
return new OrderResponse("FAILED", "Product not found", request.productId(), request.quantity(), 0);
}
if (product.quantity() < request.quantity()) {
return new OrderResponse("FAILED", "Insufficient stock. Available: " + product.quantity(), request.productId(), request.quantity(), 0);
}
double totalPrice = product.price() * request.quantity();
return new OrderResponse("CONFIRMED", "Order placed successfully for " + product.name(), request.productId(), request.quantity(), totalPrice);
}
}
Test the full flow
All the code is in place. Time to start the Order Service and send a real order through the entire system.
- Open a new terminal in VS Code (make sure you are in the order-service folder).
- Start the Order Service by running this command:
./mvnw spring-boot:run
What should I see?
After a few seconds of downloading dependencies and compiling, you should see log output ending with a line like Started OrderServiceApplication and the service running on port 8081.
- Open http://localhost:8761 in your browser to check the Eureka Dashboard.
- Confirm that both INVENTORY-SERVICE and ORDER-SERVICE appear under Instances currently registered with Eureka.
Now send an order request to test inter-service communication.
- Open another terminal tab and send a test order by running this command:
curl -X POST http://localhost:8081/api/orders -H "Content-Type: application/json" -d '{"productId": 1, "quantity": 2}'
What should I see?
You should receive a JSON response with "status":"CONFIRMED" and a total price of 1999.98 (two laptops at 999.99 each). The Order Service discovered the Inventory Service through Eureka, checked stock, and confirmed the order.
- Try ordering more than the available stock by running:
curl -X POST http://localhost:8081/api/orders -H "Content-Type: application/json" -d '{"productId": 1, "quantity": 999}'
This time you should see a FAILED response with a message about insufficient stock. The system correctly validates availability before confirming.
Getting a connection error or 500 response?
- Make sure all three services are running: Discovery Server on port 8761, Inventory Service on port 8082, and Order Service on port 8081.
- If the Order Service just started, wait 30 seconds for it to finish registering with Eureka and fetching the service registry.
- Check that the spring.application.name in Inventory Service is exactly inventory-service (lowercase with a hyphen). The Feign client looks up this exact name.
Help me troubleshoot my Order Service connection to Inventory Service
Your microservices are communicating through service discovery. The Order Service finds the Inventory Service by name, with zero hardcoded URLs. Next up, you will make this system resilient by adding a circuit breaker that handles failures gracefully.
Secret mission
Add Circuit Breaker Resilience
Your Order Service works great when the Inventory Service is healthy. But what happens when it goes down? Right now, the Order Service crashes with an ugly error. In this secret mission, you'll add a Resilience4j circuit breaker that gracefully handles failures and keeps your system responsive even when downstream services are unavailable.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine, so there are no ongoing cloud costs or subscriptions. Decide whether to keep your services running, stop them to free up system resources, or delete everything entirely.
Resources you used:
- Discovery Server running on port 8761.
- Inventory Service running on port 8082.
- Order Service running on port 8081.
- Project folders: discovery-server, inventory-service, and order-service.
- JDK 21 (Eclipse Temurin) installed via Homebrew.
Keep everything running
No action needed. Choose this if you're still actively experimenting with your microservices system.
- All three services continue running on ports 8761, 8082, and 8081.
- JDK 21 and your VS Code extensions remain installed for future Java projects.
- Your project folders stay on disk for continued development.
Pause - I'll come back to this later
Stop the running services to free up memory and ports. Your project files stay on disk so you can restart later.
- Switch to the terminal running the Discovery Server and press Ctrl+C to stop it.
- Switch to the terminal running the Inventory Service and press Ctrl+C to stop it.
- Switch to the terminal running the Order Service and press Ctrl+C to stop it.
To restart later, run ./mvnw spring-boot:run inside each project folder. Start the Discovery Server first, then the other two services.
Delete - I don't want to use this again
Remove all project files and optionally uninstall JDK 21. This is a full cleanup.
- Stop all three services by pressing Ctrl+C in each terminal window.
- Delete the project folders by running this command (macOS/Linux):
rm -rf ~/Desktop/discovery-server ~/Desktop/inventory-service ~/Desktop/order-service
- On Windows, run this instead:
rmdir /s /q %USERPROFILE%\Desktop\discovery-server %USERPROFILE%\Desktop\inventory-service %USERPROFILE%\Desktop\order-service
- To uninstall JDK 21, run:
brew uninstall --cask temurin@21
- To remove the VS Code extensions, open VS Code, press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows), find Extension Pack for Java and Spring Boot Extension Pack, then click Uninstall on each.
That's a Wrap!
That's a Wrap!
Nice work! You just built a production-grade microservices system from the ground up, complete with dynamic service discovery, declarative inter-service communication, and fault tolerance.
You've learned how to:
- Build a Eureka Discovery Server that acts as a service registry, letting microservices find each other dynamically without hardcoded URLs.
- Create independent Spring Boot microservices (Order Service and Inventory Service) that register themselves with Eureka and expose REST APIs.
- Wire up OpenFeign declarative HTTP clients so the Order Service discovers and calls the Inventory Service by name through the service registry.
- Secret Mission: Add a Resilience4j circuit breaker that gracefully handles service failures with a fallback response, preventing cascading outages across your system.
Ready to quiz yourself?