Hello! Welcome back to our microservices journey.
In our last lesson, we successfully set up a service registry with Eureka, allowing our microservices to find and communicate with each other using logical names. This solved the internal communication problem. However, a new challenge arises: how should external clients, like a web or mobile application, access our collection of services? Exposing every service directly is impractical, insecure, and creates a nightmare for the client-side developer.
This lesson tackles that exact problem by addressing the learning outcome: Implement a centralized API Gateway using Spring Cloud Gateway for routing. We will build a single, robust "front door" for our entire microservices system. This gateway will be responsible for intelligently routing incoming requests to the correct downstream service.
By the end of this ~60-minute lesson, you will be able to:
- Explain the role and benefits of an API Gateway in a microservices architecture.
- Create a new service using Spring Cloud Gateway.
- Configure routing rules to forward requests to backend services, both statically and dynamically using our Eureka service registry.
- Define routes using both YAML configuration and the Java Fluent API.
- Monitor your gateway's configuration for debugging and verification.
Let's dive in and build the main entry point to our distributed system.
The Role of an API Gateway
An API Gateway is a server that acts as a single entry point into a system. It's an architectural pattern that sits between the client applications and the collection of backend microservices.

The key responsibilities of a gateway include:
- Reverse Proxy & Routing: It forwards client requests to the appropriate backend service.
- Single Point of Entry: Clients only need to know the address of the gateway, not every individual microservice.
- Cross-Cutting Concerns: It's the perfect place to handle logic that applies to multiple services, such as authentication, SSL termination, rate limiting, logging, and caching.
Building an API Gateway in Java with Spring Cloud Gateway
To get a clear overview of what an API Gateway does and the problems it solves, watch this introductory segment from Dan Vega's video, 'Building an API Gateway in Java with Spring Cloud Gateway'.
Watch the video from the beginning until 04:33. Pay close attention to the diagram and the discussion of cross-cutting concerns.
Step 1: Creating Your First API Gateway
Let's create a new Spring Boot application to act as our gateway.
- Create the Project: Navigate to start.spring.io.
- Add Dependencies: You will need two key dependencies:
- Gateway: This includes the core Spring Cloud Gateway functionality.
- Eureka Discovery Client: This allows our gateway to connect to the Eureka server we built in the previous lesson, enabling it to discover other services.
- Generate and open the project in your IDE.
The following clip from the 'API Gateway | Microservice' video by Telusko shows exactly which dependencies to select. Note that he also adds 'actuator' and 'devtools', which are good practices but not strictly required for this first step.
Watch from 02:16 to 03:44 to see the project setup on Spring Initializr.
Step 2: Configuring Routes
The primary job of our gateway is routing. A Route in Spring Cloud Gateway is defined by an ID, a destination URI, a collection of Predicates, and a collection of Filters.
- Predicate: A condition that must be met for the route to be matched. For example, "does the request path match
/api/users/**?". - Filter: An action to modify the request or response. For example, "add a header to the request" or "rewrite the path before forwarding".
We will explore two ways to configure routes: using the application.yml file and using the Java Fluent API.
Method 1: Static Routing with application.yml
This is the most common approach. We'll start by configuring a route to the service-a we built in the previous lesson.
First, let's configure our gateway application itself. Open src/main/resources/application.properties, rename it to application.yml, and add the following configuration:
# Give the gateway application a name and port
server:
port: 8080
spring:
application:
name: api-gateway
# Tell the gateway where to find the Eureka server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
Now, let's add our first route. We'll statically route requests from /service-a/** on the gateway to our service-a microservice. Assume for a moment service-a is running on localhost:8081.
spring:
# ... (other properties) ...
cloud:
gateway:
routes:
- id: service-a-route # A unique ID for the route
uri: http://localhost:8081 # The destination
predicates:
- Path=/service-a/** # If the path starts with /service-a
This configuration tells the gateway: "If a request comes in with a path like /service-a/hello, forward it to http://localhost:8081/service-a/hello".
Test your understanding!
With the configuration above, a request to http://localhost:8080/service-a/hello works. However, service-a actually exposes its endpoint at /hello, not /service-a/hello. How could you modify the route to make this work? (Hint: You'll need a filter).
Show answer
You would use the RewritePath or StripPrefix filter. For example, StripPrefix=1 would remove the first path segment (/service-a) before forwarding the request.
spring:
cloud:
gateway:
routes:
- id: service-a-route
uri: http://localhost:8081
predicates:
- Path=/service-a/**
filters:
- StripPrefix=1
This would transform a request from /service-a/hello into /hello before sending it to localhost:8081.
Method 2: Dynamic Routing with Eureka
The static configuration above has a flaw: we've hardcoded the address http://localhost:8081. This defeats the purpose of our service registry! The better way is to tell the gateway to look up the service's address from Eureka.
To do this, we modify the uri and use the special lb:// (load-balancer) protocol. This tells the gateway to use the service ID from Eureka.
spring:
cloud:
gateway:
routes:
- id: service-a-route
uri: lb://service-a # Use Eureka service ID!
predicates:
- Path=/service-a/**
filters:
- StripPrefix=1 # Remove '/service-a' before forwarding
- id: service-b-route
uri: lb://service-b
predicates:
- Path=/service-b/**
filters:
- StripPrefix=1 # Remove '/service-b' before forwarding
With this setup, the gateway will ask Eureka for the current location of service-a or service-b and route the request dynamically. This is much more resilient.
Building an API Gateway in Java with Spring Cloud Gateway
The following video from Dan Vega provides a detailed walkthrough of configuring routes in application.yml. While he uses hardcoded URIs, the principles of defining the ID, URI, predicates, and filters are perfectly explained.
Watch from 09:14 to 18:03. This will solidify your understanding of how to structure routes in YAML and introduce powerful built-in predicates and filters.
Test Your Setup
- Start your Eureka Server.
- Start
service-a(from the previous lesson, which has a/helloendpoint). - Start
service-b(from the previous lesson, which has a/call-aendpoint). - Start your new
api-gatewayapplication. - Open your browser and navigate to
http://localhost:8080/service-a/hello. You should see "Hello from Service A!". - Now navigate to
http://localhost:8080/service-b/call-a. You should again see "Hello from Service A!", demonstrating that the gateway routed toservice-b, which then calledservice-avia Eureka.
Step 3: Alternative Configuration with the Java Fluent API
For more complex or conditional routing logic, you can define routes programmatically in a Java configuration file.
Create a new Java class for this configuration:
package com.example.apigateway;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("service-a-java-route", r -> r.path("/service-a/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://service-a"))
.route("service-b-java-route", r -> r.path("/service-b/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://service-b"))
.build();
}
}
Note: You should typically choose one method of configuration (YAML or Java) for clarity, but they can be combined. If a route is defined in both, the Java configuration often takes precedence.
Lab 1: Routing - Configure & Monitor Gateway Routes
This hands-on lab from Andi Falk provides clear examples for both YAML and Java configuration. We'll focus on the Java API section to see a concrete example.
Read the section 'Step 5: Configure routes with the Fluent Java Routes API'. It provides another example of a RouteLocator bean and reinforces the concepts of path, filters, and URI.
Step 4: Monitoring Your Gateway Routes
How can you be sure your routes are loaded correctly? If you included the Spring Boot Actuator dependency, Spring Cloud Gateway provides a helpful endpoint.
Navigate to http://localhost:8080/actuator/gateway/routes.
You will see a JSON response detailing all the routes currently active in your gateway, including their predicates and filters. This is an invaluable tool for debugging.
Lab 1: Routing - Configure & Monitor Gateway Routes
The lab guide also explains how to enable and use this actuator endpoint.
Read the final section, 'Step 6: Monitor routes and route metrics', to understand how to enable and interpret the output from the gateway's actuator endpoints.
Conclusion
Fantastic work! You have now established a proper entry point into your microservices ecosystem. By centralizing request handling with an API Gateway, you've made your system more secure, manageable, and easier for client applications to consume.
Key Takeaways:
- An API Gateway acts as a reverse proxy and single entry point for a microservices architecture.
- Spring Cloud Gateway allows you to define Routes that consist of Predicates (conditions) and Filters (actions).
- Routes can be configured declaratively in
application.ymlor programmatically using the Java Fluent API. - For a resilient system, you should use dynamic routing by integrating the gateway with a service registry like Eureka, using the
lb://<service-name>URI format. - The
/actuator/gateway/routesendpoint is essential for monitoring and debugging your gateway's configuration.
What's Next?
Our gateway can now successfully route traffic into our system. The next step is to refine how our services talk to each other. In the upcoming lesson, "Implement synchronous inter-service communication using RestTemplate or Feign," we will dive deeper into the RestTemplate we used previously and introduce a powerful, declarative alternative called Feign Client, which makes writing inter-service communication code even cleaner and simpler.