Spring Boot 3 API Gateway with Eureka

Discover how to integrate Spring Boot 3 API Gateway with Spring Cloud Netflix Eureka Server for seamless communication and microservices architecture.

An API Gateway is a crucial intermediary in microservices, serving as the single entry point for client requests. It acts as a gatekeeper, routing and load-balancing incoming traffic to the appropriate backend services. By centralizing authentication, authorization, and other cross-cutting concerns, the API Gateway simplifies the management and security of the developer applications.

The API Gateway is pivotal in facilitating communication between clients and the service instances registered with the Eureka server when working with Eureka Springs. By leveraging Eureka Springs’ service discovery capabilities, the API Gateway can dynamically route requests to the appropriate service instances, ensuring optimal load distribution and failover mechanisms.

Moreover, the API Gateway is a powerful tool for implementing cross-cutting concerns such as rate limiting, caching, and request transformation. This centralized approach enhances the developer application’s security and performance and promotes code reusability and maintainability.

Key Benefits.

  • Simplified API Management: No more juggling multiple endpoints.
  • Enhanced Security: Fortify your defenses with centralized authentication and authorization.
  • Efficient Load Balancing: Distribute incoming traffic evenly, ensuring smooth sailing for your services.

Ready to get started? Dive in and experience the magic of Spring Boot API Gateway and Spring Cloud Netflix Eureka for yourself.

Set up the Eureka server and client.

Set up the API Gateway.

This example demonstrates the seamless integration of Eureka Springs, API Gateway, and Spring Boot, enabling us to quickly build scalable, fault-tolerant, and highly available applications.

1. Initial project with spring initializr

2. Add Dependencies.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.3</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.example</groupId>
 <artifactId>api-gateway</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>api-gateway</name>
 <description>Demo project for Spring Boot</description>
 <url/>
 <licenses>
  <license/>
 </licenses>
 <developers>
  <developer/>
 </developers>
 <scm>
  <connection/>
  <developerConnection/>
  <tag/>
  <url/>
 </scm>
 <properties>
  <java.version>21</java.version>
  <spring-cloud.version>2023.0.3</spring-cloud.version>
 </properties>
 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-netflix-eureka-client</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>


  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <scope>runtime</scope>
   <optional>true</optional>
  </dependency>
  <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>
 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
     <excludes>
      <exclude>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
      </exclude>
     </excludes>
    </configuration>
   </plugin>
  </plugins>
 </build>

</project>

3. Next, the developer creates a Spring Boot application that will act as an application API Gateway, leveraging the power of Eureka Springs for service discovery.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {

     public static void main(String[] args) {
      SpringApplication.run(ApiGatewayApplication.class, args);
     }

}

4. Configure application.properties or application.yml for API Gateway

server:
  port: 9090

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: eureka-client
          uri: lb://EUREKA-CLIENT
          predicates:
            - Path=/**
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    instance:
      lease-renewal-interval-in-seconds: 30   # Frequency (in seconds) at which the client sends heartbeats to the Eureka server
      lease-expiration-duration-in-seconds: 90 # Time (in seconds) after which the Eureka server considers the instance expired if no heartbeat is received
management:
  endpoint:
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: '*'
logging:
  level:
    root: INFO
    org.springframework.cloud.gateway: DEBUG
    org.springframework.web: DEBUG
    reactor.netty: DEBUG

Configuration Explanation.

spring.cloud.gateway.discovery.locator.enabled: Enables the use of service discovery to locate routes dynamically. When set to true, Spring Cloud Gateway will discover routes using the services registered with Eureka.

spring.cloud.gateway.discovery.locator.lower-case-service-id: When set to trueThis converts all service IDs to lowercase. This can help avoid case-sensitivity issues when matching service IDs in a case-insensitive environment.

spring.cloud.gateway.routes: Defines the static routes for the gateway. In this configuration, one route is defined.

eureka.client.service-url.defaultZone: Specifies the URL of the Eureka server where the application should register itself and where it should send heartbeat messages. The defaultZone point is the Eureka server URL.

eureka.instance.lease-renewal-interval-in-seconds: Sets the frequency (in seconds) at which the application sends heartbeat messages to the Eureka server. This is set to 30 seconds, meaning the client will send a heartbeat every 30 seconds.

eureka.instance.lease-expiration-duration-in-seconds: Defines the time (in seconds) after which the Eureka server will consider the instance as “down” or unavailable if it does not receive a heartbeat. It is set to 90 seconds. If the Eureka server doesn’t receive a heartbeat within this period, it will mark the instance as down and remove it from the registry.

management.endpoint.gateway.enabled: Enables the Spring Cloud Gateway Actuator endpoints, allowing for monitoring and managing the gateway routes and filters through the /actuator/gateway endpoint.

management.endpoints.web.exposure.include: Specifies which actuator endpoints should be exposed over HTTP. The value '*' Includes all available actuator endpoints, making them accessible via the management port (by default, the same as the application port).

1. Create a Dockerfile.

# Stage 1: Build the JAR file
FROM maven:3.9.7-amazoncorretto-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2: Run the application
FROM amazoncorretto:21-alpine
VOLUME /tmp
COPY --from=build /app/target/*.jar api-gateway.jar
ENTRYPOINT ["java", "-jar", "/api-gateway.jar"]

2. Create a Docker compose file.

version: '3.8'

services:
  api-gateway:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: api-gateway
    environment:
      - EUREKA_CLIENT_SERVICEURL_DEFAULTZONE=http://eureka-server:8761/eureka/
    ports:
      - "9090:9090"
    networks:
      - eureka_eureka-network
networks:
  eureka_eureka-network:
    external: true

3. The developer can check the network in Docker by following this command.

>docker network ls
NETWORK ID     NAME                            DRIVER    SCOPE
2c7250cfbc7a   bridge                          bridge    local
1a78ba5571d4   eureka_eureka-network           bridge    local

4. The developer can inspect the network by following this command.

>docker inspect eureka_eureka-network
[
    {
        "Name": "eureka_eureka-network",
        "Id": "1a78ba5571d4fdd2c3a7b174f79626780f5082a33fcf7e576ef88e5705307f5a",
        "Created": "2024-09-02T11:35:30.757824047Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.24.0.0/16",
                    "Gateway": "172.24.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "17c7a20ed0300de0b2f0827a8bfc48f6ed17898924b459fe859acc3440149ce4": {
                "Name": "eureka-eureka-client-2",
                "EndpointID": "bf683251bd12e76a55f16479072481d527941c4b7f3db5d1d53268733a4084e0",
                "MacAddress": "02:42:ac:18:00:05",
                "IPv4Address": "172.24.0.5/16",
                "IPv6Address": ""
            },
            "2e7082ee05fd8f853628e352b570c1d3b040276e76ee810346186996fc0e478d": {
                "Name": "eureka-server",
                "EndpointID": "0cd0fd34acc274954e656cdb4a249ca92988c09040fdda64fdda04c882a9f266",
                "MacAddress": "02:42:ac:18:00:03",
                "IPv4Address": "172.24.0.3/16",
                "IPv6Address": ""
            },
            "52475e2369a43b6d20cc231a0c53e5bfb424baef4a349434993b170a5f798301": {
                "Name": "api-gateway",
                "EndpointID": "91cf1ba80ac6457a29fe5b26efc050bb60aa61fc93f9bad3a3be76ae641127ad",
                "MacAddress": "02:42:ac:18:00:02",
                "IPv4Address": "172.24.0.2/16",
                "IPv6Address": ""
            },
            "d40f061f7f3b6e12d961ae74c9a861e2a33fb89c20ecd110cda80a075ffcf984": {
                "Name": "eureka-eureka-client-1",
                "EndpointID": "6d0d6a56c7af45f1b8240b6ff68f2e223f1d37bf91c55d22056381a6eaba7523",
                "MacAddress": "02:42:ac:18:00:06",
                "IPv4Address": "172.24.0.6/16",
                "IPv6Address": ""
            },
            "f64012185ccae36af3a89ac5beb448572155b4b01655749794839c786d32a8cc": {
                "Name": "eureka-eureka-client-3",
                "EndpointID": "7f2bd041f3c815a1440a65ad7066d474613da26154a373dddfc6892855020b42",
                "MacAddress": "02:42:ac:18:00:04",
                "IPv4Address": "172.24.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "eureka-network",
            "com.docker.compose.project": "eureka",
            "com.docker.compose.version": "2.29.1"
        }
    }
]

5. Execute the Docker command to start the API gateway.

>docker compose up -d

The -d flag in the docker-compose up -d Command stands for “detached mode.” When you run Docker Compose with the -d flag, the containers start in the background, allowing you to continue using the terminal.

6. Check the image of the API gateway.

>docker images
REPOSITORY                                      TAG       IMAGE ID       CREATED        SIZE
api-gateway-api-gateway                         latest    da7f5af74844   20 hours ago   367MB

7. Check the container of the API gateway.

>docker container ls
CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS          PORTS                           NAMES
52475e2369a4   api-gateway-api-gateway   "java -jar /api-gate…"   20 hours ago   Up 13 minutes   0.0.0.0:9090->9090/tcp          api-gateway

Access the actuator gateway routes.

1. The developer can view the gateway by accessing the URL.

http://localhost:9090/actuator/gateway/routes
Issue.

When the developer accesses the actuator gateway routes, the gateway is not found or is a blank page. The developer should check your dependency.
artifactId should be “spring-cloud-starter-gateway”, not “spring-cloud-starter-gateway-mvc”. Do not use artifactId “spring-boot-starter-web” in the API gateway project.

When starting the API gateway server, this message will appear when the developer uses artifactId “spring-boot-starter-web” and artifactId “spring-cloud-starter-gateway” together.

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration': Failed to instantiate [org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration]: Constructor threw exception

Access the Eureka client from the API gateway.

1. The developer can access the Eureka client by accessing the URL.

>curl http://localhost:9090/hostname
f64012185cca

Alternatively, windows Powershell.

> curl http://localhost:9090/hostname

StatusCode        : 200
StatusDescription : OK
Content           : d40f061f7f3b
RawContent        : HTTP/1.1 200 OK
                    Content-Length: 12
                    Content-Type: text/plain;charset=UTF-8
                    Date: Tue, 03 Sep 2024 08:20:12 GMT

                    d40f061f7f3b
Forms             : {}
Headers           : {[Content-Length, 12], [Content-Type, text/plain;charset=UTF-8], [Date, Tue, 03 Sep 2024 08:20:12 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 12

2. The API gateway can access the Eureka client and return a response from the client.

Issue.

When the developer accesses the Eureka client and gets the message 503 Service Unavailable, the developer should check that the artifactId “spring-cloud-starter-loadbalancer” must be included in the dependency.

1. Response on Windows PowerShell without a load balancer.

> curl http://localhost:9090/hostname
{"timestamp":"2024-09-03T08:25:29.982+00:00","path":"/hostname","status":503,"error":"Service Unavailable","requestId":"07c0cba7-1","message":"Unable to find
instance for EUREKA-CLIENT","trace":"org.springframework.cloud.gateway.support.NotFoundException: 503 SERVICE_UNAVAILABLE \"Unable to find instance for
EUREKA-CLIENT\"\r\n\tat org.springframework.cloud.gateway.support.NotFoundException.create(NotFoundException.java:45)\r\n\tSuppressed: The stacktrace has been
enhanced by Reactor, refer to additional information below: \r\nError has been observed at the following site(s):\r\n\t*__checkpoint ⇢
org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]\r\n\t*__checkpoint ⇢ HTTP GET \"/hostname\"
...

2. Response on the Postman without a load balancer.

{
    "timestamp": "2024-09-03T08:27:19.513+00:00",
    "path": "/hostname",
    "status": 503,
    "error": "Service Unavailable",
    "requestId": "4a06906b-1",
    "message": "Unable to find instance for EUREKA-CLIENT",
    "trace": ...
}

Conclusion

The API Gateway acts as a unified entry point for clients, abstracting the complexities of the underlying microservices architecture. It offers a range of benefits, including routing, authentication, authorization, monitoring, load balancing, and protocol translation, ensuring a consistent and secure interface for clients.

Finally

By combining these powerful tools, the developer can unlock new application agility, scalability, and reliability levels. Whether building a small-scale project or a large-scale enterprise system, the Spring Boot ecosystem provides a solid foundation for embracing the microservices paradigm and delivering exceptional software solutions. The developer can improve the security of the Spring boot API gateway by using JSON Web Tokens (JWT) or performance by using a rate limiter.

Leave a Comment

Your email address will not be published. Required fields are marked *