Spring Cloud - Quick Guide



Spring Cloud - Introduction

Before we look at Spring Cloud, lets have a brief overview on Microservice Architecture and the role of Spring Boot in creating microservices.

Microservice Architecture

Microservice architecture is a style of application development where the application is broken down into small services and these services have loose coupling among them. Following are the major advantages of using microservice architecture −

  • Easy to maintain − Microservices are small in size and are supposed to handle only single business task. So, they are simple to develop and maintain.

  • Independent Scaling & Deployment − Microservices have their individual deployment pattern and cadence. So, each service can be scaled based on the load which that service is supposed to cater to. Each service can be deployed based on its schedule.

  • Independent Technology Usage − Microservices have their code base segregated from the deployment environment, so the language and the technology that a microservice needs to use can be decided based on the use-case. There is no need to have a common stack to be used in all microservices.

More details about Microservice Architecture can be found at Microservice Architecture

Spring Boot

Spring Boot is a Java-based framework which is used to create microservices which are used in microservice architecture. It further brings down the time needed to develop a Spring application. Following are the major benefits it provides −

  • It is easy to understand and develop a Spring application

  • Increases productivity

  • Reduces the development time

More info on Spring Boot can be found at −Spring Boot

Spring Cloud

Spring Cloud provides a collection of components which are useful in building distributed applications in cloud. We can develop these components on our own, however that would waste time in developing and maintaining this boilerplate code.

That is where Spring Cloud comes into picture. It provides ready-to-use cloud patterns for common problems which are observed in a distributed environment. Some of the patterns which it attempts to address are −

  • Distributed Messaging

  • Load Balancing

  • Circuit Breakers

  • Routing

  • Distributed Logging

  • Service Registration

  • Distributed Lock

  • Centralized Configuration

That is why, it becomes a very useful framework in developing applications which require high scalability, performance, and availability.

In this tutorial, we are going to cover the above-listed components of Spring Cloud.

Benefits of Using Spring Cloud

  • Developers focus on Business Logic − Spring Cloud provides all the boilerplate code to implement common design patterns of the cloud. Developers thus can focus on the business logic without the need to develop and maintain this boilerplate code.

  • Quick Development Time − As the developers get the boilerplate for free, they can quickly deliver on the required projects while maintaining code quality.

  • Easy to use − Spring Cloud projects can easily be integrated with existing Spring Projects.

  • Active Project − Spring Cloud is actively maintained by Pivotal that is the company behind Spring. So, we get all the new features and bug-fixes for free just by upgrading the Spring Cloud version.

Microservice architecture has multiple advantages; however, one of its most critical drawbacks is its deployment in a distributed environment. And with the distributed systems, we have some common problems that frequently creep up, for example −

  • How does service A know where to contact service B, i.e., address of service B?

  • How do multiple services communicate with each other, i.e., what protocol to use?

  • How do we monitor various services in our environment?

  • How do we distribute the configuration of the service with the service instance?

  • How do we link the calls which travel across services for debugging purposes?

  • and so on

These are the set of problems which Spring Cloud tries to address and provide common solution to.

While Spring Boot is used for quick application development, using it along with Spring Cloud can reduce time to integrate our microservices which we develop and deploy in a distributed environment.

Spring Cloud Components

Let us now take a look at the various components which Spring Cloud provides and the problems these components solve

Problem Components
Distributed Cloud Configuration Spring Cloud Configuration, Spring Cloud Zookeeper, Spring Consul Config
Distributed Messaging Spring Stream with Kafka, Spring Stream with RabbitMQ
Service Discovery Spring Cloud Eureka, Spring Cloud Consul, Spring Cloud Zookeeper
Logging Spring Cloud Zipkin, Spring Cloud Sleuth
Spring Service Communication Spring Hystrix, Spring Ribbon, Spring Feign, Spring Zuul

We will look at a few of these components in the upcoming chapters.

Difference between Spring Cloud and Spring Boot

This a very common question that arises when starting with Spring Cloud. Actually, there is no comparison here. Both Spring Cloud and Spring Boot are used to achieve different goals.

Spring Boot is a Java framework which is used for quicker application development, and is specifically used in Microservice architecture.

Spring cloud is used for integrating these microservices so that they can easily work together in a distributed environment and can communicate with each other

In fact, to avail maximum benefits like less development time, it is recommended to use Spring Boot along with Spring Cloud.

Spring Cloud - Dependency Management

In this chapter, we will build our very first application using Spring Cloud. Let's go over the project structure and the dependency setup for our Spring Cloud Application while using Spring Boot as the base framework.

Core Dependency

Spring Cloud group has multiple packages listed as dependency. In this tutorial, we will be using multiple packages from the Spring Cloud group. To avoid any compatibility issue between these packages, let us use Spring Cloud dependency management POM, given below −

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-dependencies</artifactId>
         <version>2025.0.0</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>

The Gradle user can achieve the same by using the following −

buildscript {
   dependencies {
      classpath "io.spring.gradle:dependency-management-plugin:1.0.10.RELEASE"
   }
}
apply plugin: "io.spring.dependency-management"
dependencyManagement {
   imports {
   mavenBom "org.springframework.cloud:spring-cloud-dependencies:
'2025.0.0')"
   }
}

Project Architecture and Structure

For this tutorial, we will use the case of a Restaurant −

  • Restaurant Service Discovery − Used for registering the service address.

  • Restaurant Customer Service − Provides Customer information to the client and other services.

  • Restaurant Service − Provides Restaurant information to the client. Uses Customer service to get city information of the customer.

  • Restaurant Gateway − Entry point for our application. However, we will use this only once in this tutorial for simplicity sake.

On a high level, here is the project architecture −

Project Architecture

And we will have the following project structure. Note that we will look at the files in the upcoming chapters.

Project Structure

Project POM

For simplicity sake, we will be using Maven-based builds. Below is the base POM file, which we will use for this tutorial.

<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorials.point</groupId>
   <artifactId>spring-cloud-eureka-client</artifactId>
   <version>1.0</version>
   <packaging>jar</packaging>
   <properties>
      <maven.compiler.source>21</maven.compiler.source>
      <maven.compiler.target>21</maven.compiler.target>
   </properties>
   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2025.0.0</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.5.6</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
               <execution>
                  <goals>
                     <goal>repackage</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
</project>

Points to note −

  • The POM dependency management section almost includes all the projects which we require.We will add the dependency section as and when we require.

  • We will use Spring Boot as the base Framework for the development of our application and that is why you see it listed as a dependency.

Spring Cloud - Service Discovery

Introduction

Service discovery is one of the most critical parts when an application is deployed as microservices in the cloud. This is because for any use operation, an application in a microservice architecture may require access to multiple services and the communication amongst them.

Service discovery helps tracking the service address and the ports where the service instances can be contacted to. There are three components at play here −

  • Service Instances − Responsible to handle incoming request for the service and respond to those requests.

  • Service Registry − Keeps track of the addresses of the service instances. The service instances are supposed to register their address with the service registry.

  • Service Client − The client which wants access or wants to place a request and get response from the service instances. The service client contacts the service registry to get the address of the instances.

Apache Zookeeper, Eureka and Consul are a few well-known components which are used for Service Discovery. In this tutorial, we will use Eureka

Dependencies for Eureka Server/Registry

For setting up Eureka Server, we need to update the POM file to contain the following dependency −

<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>

Spring Cloud - Creating Eureaka Server

Eureka Server is an application that holds the information about all client-service applications. Every Micro service will register into the Eureka server and Eureka server knows all the client applications running on each port and IP address. Eureka Server is also known as Discovery Server.

In this chapter, we will learn in detail about How to build a Eureka server.

Example - Building a Eureka Server

Eureka Server comes with the bundle of Spring Cloud. For this, we need to develop the Eureka server and run it on the default port 8761.

Visit the Spring Initializer homepage https://start.spring.io/ and download the Spring Boot project with Eureka server dependency. It is shown in the screenshot below −

Build Eureka Server

After downloading the project in main Spring Boot Application class file, we need to add @EnableEurekaServer annotation. The @EnableEurekaServer annotation is used to make your Spring Boot application acts as a Eureka Server.

The code for main Spring Boot application class file is as shown below −

EurekaserverApplication.java

package com.tutorialspoint.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaserverApplication {
   public static void main(String[] args) {
      SpringApplication.run(EurekaserverApplication.class, args);
   }
}

Make sure Spring cloud Eureka server dependency is added in your build configuration file.

The code for Maven user dependency is shown below −

<dependency>
<groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

The code for Gradle user dependency is given below −

compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')

The complete build configuration file is given below −

Maven - pom.xml

<?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.5.6</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>eurekaserver</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>eurekaserver</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>2025.0.0</spring-cloud.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </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>
         </plugin>
      </plugins>
   </build>
</project>

Gradle build.gradle

buildscript {
   ext {
      springBootVersion = '3.5.6'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 21

repositories {
   mavenCentral()
}
ext {
   springCloudVersion = '2025.0.0'
}
dependencies {
   compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

By default, the Eureka Server registers itself into the discovery. You should add the below given configuration into your application.properties file or application.yml file.

In production, one would have more than one node for registry for its high availability. Thats is where we need peer-to-peer communication between registries. As we are executing this in standalone mode, we can simply set client properties to false to avoid any errors.

application.properties file is given below −

eureka.client.registerWithEureka = false
eureka.client.fetchRegistry = false
server.port = 8761

The application.yml file is given below −

eureka:
   client:
      registerWithEureka: false
      fetchRegistry: false
server:
   port: 8761

Output

Now, you can create an executable JAR file, and run the Spring Boot application by using the Maven or Gradle commands shown below −

For Maven, use the command as shown below −

mvn clean install

After "BUILD SUCCESS", you can find the JAR file under the target directory.

For Gradle, you can use the command shown below −

gradle clean build

After "BUILD SUCCESSFUL", you can find the JAR file under the build/libs directory.

Now, run the JAR file by using the following command −

 java jar <JARFILE> 

You can find that the application has started on the Tomcat port 8761 as shown below −

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.6)

2025-10-07T14:30:11.544+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : Starting EurekaserverApplication using Java 21.0.6 with PID 46404 (D:\Projects\eurekaserver\target\classes started by mahes in D:\Projects\eurekaserver)
2025-10-07T14:30:11.546+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : No active profile set, falling back to 1 default profile: "default"
2025-10-07T14:30:12.957+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=37b0aecd-4feb-3fa0-b028-b62a23271b1d
2025-10-07T14:30:13.383+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8761 (http)
2025-10-07T14:30:13.399+05:30  INFO 46404 --- [eurekaserver] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-10-07T14:30:13.399+05:30  INFO 46404 --- [eurekaserver] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.46]
2025-10-07T14:30:13.459+05:30  INFO 46404 --- [eurekaserver] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-10-07T14:30:13.459+05:30  INFO 46404 --- [eurekaserver] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1789 ms
2025-10-07T14:30:14.623+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2025-10-07T14:30:14.624+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2025-10-07T14:30:14.878+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2025-10-07T14:30:14.878+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2025-10-07T14:30:15.173+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.v.b.OptionalValidatorFactoryBean     : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
2025-10-07T14:30:15.985+05:30  WARN 46404 --- [eurekaserver] [           main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-10-07T14:30:16.010+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2025-10-07T14:30:16.064+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Initializing Eureka in region us-east-1
2025-10-07T14:30:16.064+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Client configured to neither register nor query for data.
2025-10-07T14:30:16.069+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1759827616067 with initial instances count: 0
2025-10-07T14:30:16.154+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
2025-10-07T14:30:16.156+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Adding new peer nodes [http://localhost:8761/eureka/]
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2025-10-07T14:30:16.424+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Replica node URL:  http://localhost:8761/eureka/
2025-10-07T14:30:16.435+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: []
2025-10-07T14:30:16.436+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
2025-10-07T14:30:16.453+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2025-10-07T14:30:16.536+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application EUREKASERVER with eureka with status UP
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
2025-10-07T14:30:16.553+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2025-10-07T14:30:16.576+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8761 (http) with context path '/'
2025-10-07T14:30:16.578+05:30  INFO 46404 --- [eurekaserver] [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
2025-10-07T14:30:16.604+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : Started EurekaserverApplication in 5.669 seconds (process running for 6.405)

Now, hit the URL http://localhost:8761/ in your web browser and you can find the Eureka Server running on the port 8761 as shown below −

Eureka Server Running on port 8761

Spring Cloud - Creating Eureaka Client

In this chapter, you are going to learn in detail about how to register the Spring Boot Micro service application into the Eureka Server. Before registering the application, please make sure Eureka Server is running on the port 8761 or first build the Eureka Server and run it. For further information on building the Eureka server, you can refer to the previous chapter Spring Cloud - Creating Eureka Server

Example - Building a Eureka Client

Eureka Client comes with the bundle of Spring Cloud. For this, we need to develop the Eureka client and run it on the default port 8080.

Visit the Spring Initializer homepage https://start.spring.io/ and download the Spring Boot project with Eureka Client dependency. It is shown in the screenshot below −

Build Eureka Client

After downloading the project in main Spring Boot Application class file, we need to add @EnableEurekaClient annotation. The @EnableEurekaClient annotation is used to make your Spring Boot application acts as a Eureka Client.

EurekaclientApplication.java

The code for main Spring Boot application class file is as shown below −

package com.tutorialspoint.eurekaclient;

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

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaclientApplication {
   public static void main(String[] args) {
      SpringApplication.run(EurekaclientApplication.class, args);
   }
}

Make sure Spring cloud Eureka client dependency is added in your build configuration file.

The code for Maven user dependency is shown below −

<dependency>
<groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

The code for Gradle user dependency is given below −

compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')

The complete build configuration file is given below −

Maven - pom.xml

<?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.5.6</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>eurekaserver</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>eurekaserver</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>2025.0.0</spring-cloud.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </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>
         </plugin>
      </plugins>
   </build>
</project>

Gradle - build.gradle

buildscript {
   ext {
      springBootVersion = '3.5.6'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 21

repositories {
   mavenCentral()
}
ext {
   springCloudVersion = '2025.0.0'
}
dependencies {
   compile('org.springframework.cloud:spring-cloud-starter-eureka-client')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

To register the Spring Boot application into Eureka Server we need to add the following configuration in our application.properties file or application.yml file and specify the Eureka Server URL in our configuration.

application.yml

The code for application.yml file is given below −

eureka:
   client:
      serviceUrl:
         defaultZone: http://localhost:8761/eureka
      instance:
      preferIpAddress: true
spring:
   application:
      name: eurekaclient

application.properties

The code for application.properties file is given below −

eureka.client.serviceUrl.defaultZone  = http://localhost:8761/eureka
eureka.client.instance.preferIpAddress = true
spring.application.name = eurekaclient

Now, add the Rest Endpoint to return String in the main Spring Boot application and the Spring Boot Starter web dependency in build configuration file. Observe the code given below −

EurekaclientApplication.java

package com.tutorialspoint.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class EurekaclientApplication {

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

   @GetMapping(value = "/")
   public String home() {
      return "Eureka Client application";
   }
}

Output

You can create an executable JAR file, and run the Spring Boot application by using the following Maven or Gradle commands −

For Maven, you can use the following command −

mvn clean install

After "BUILD SUCCESS", you can find the JAR file under the target directory.

For Gradle, you can use the following command −

gradle clean build

After "BUILD SUCCESSFUL", you can find the JAR file under the build/libs directory.

Now, run the JAR file by using the command as shown −

java jar <JARFILE> 

Now, the application has started on the Tomcat port 8080 and Eureka Client application is registered with the Eureka Server as shown below −


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.6)

2025-10-07T14:30:11.544+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : Starting EurekaserverApplication using Java 21.0.6 with PID 46404 (D:\Projects\eurekaserver\target\classes started by mahes in D:\Projects\eurekaserver)
2025-10-07T14:30:11.546+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : No active profile set, falling back to 1 default profile: "default"
2025-10-07T14:30:12.957+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=37b0aecd-4feb-3fa0-b028-b62a23271b1d
2025-10-07T14:30:13.383+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8761 (http)
2025-10-07T14:30:13.399+05:30  INFO 46404 --- [eurekaserver] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-10-07T14:30:13.399+05:30  INFO 46404 --- [eurekaserver] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.46]
2025-10-07T14:30:13.459+05:30  INFO 46404 --- [eurekaserver] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-10-07T14:30:13.459+05:30  INFO 46404 --- [eurekaserver] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1789 ms
2025-10-07T14:30:14.623+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2025-10-07T14:30:14.624+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2025-10-07T14:30:14.878+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2025-10-07T14:30:14.878+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2025-10-07T14:30:15.173+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.v.b.OptionalValidatorFactoryBean     : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
2025-10-07T14:30:15.985+05:30  WARN 46404 --- [eurekaserver] [           main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-10-07T14:30:16.010+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2025-10-07T14:30:16.064+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Initializing Eureka in region us-east-1
2025-10-07T14:30:16.064+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Client configured to neither register nor query for data.
2025-10-07T14:30:16.069+05:30  INFO 46404 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1759827616067 with initial instances count: 0
2025-10-07T14:30:16.154+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
2025-10-07T14:30:16.156+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Adding new peer nodes [http://localhost:8761/eureka/]
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2025-10-07T14:30:16.379+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2025-10-07T14:30:16.424+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Replica node URL:  http://localhost:8761/eureka/
2025-10-07T14:30:16.435+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: []
2025-10-07T14:30:16.436+05:30  INFO 46404 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
2025-10-07T14:30:16.453+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2025-10-07T14:30:16.536+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application EUREKASERVER with eureka with status UP
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
2025-10-07T14:30:16.550+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
2025-10-07T14:30:16.553+05:30  INFO 46404 --- [eurekaserver] [       Thread-9] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2025-10-07T14:30:16.576+05:30  INFO 46404 --- [eurekaserver] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8761 (http) with context path '/'
2025-10-07T14:30:16.578+05:30  INFO 46404 --- [eurekaserver] [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
2025-10-07T14:30:16.604+05:30  INFO 46404 --- [eurekaserver] [           main] c.t.e.EurekaserverApplication            : Started EurekaserverApplication in 5.669 seconds (process running for 6.405)
2025-10-07T14:30:57.836+05:30  INFO 46404 --- [eurekaserver] [nio-8761-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-10-07T14:30:57.836+05:30  INFO 46404 --- [eurekaserver] [nio-8761-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-10-07T14:30:57.842+05:30  INFO 46404 --- [eurekaserver] [nio-8761-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms
2025-10-07T14:31:16.552+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 0ms
2025-10-07T14:32:16.553+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 13ms
2025-10-07T14:33:16.553+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 9ms
2025-10-07T14:34:16.560+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 15ms
2025-10-07T14:35:16.560+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 9ms
2025-10-07T14:36:16.561+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 9ms
2025-10-07T14:37:16.562+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 9ms
2025-10-07T14:38:16.564+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 11ms
2025-10-07T14:39:16.564+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 8ms
2025-10-07T14:40:16.565+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 9ms
2025-10-07T14:41:16.567+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 11ms
2025-10-07T14:42:16.573+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 14ms
2025-10-07T14:43:16.578+05:30  INFO 46404 --- [eurekaserver] [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 14ms
2025-10-07T14:43:22.871+05:30  INFO 46404 --- [eurekaserver] [io-8761-exec-10] c.n.e.registry.AbstractInstanceRegistry  : Registered instance EUREKACLIENT/Home:eurekaclient with status UP (replication=false)
2025-10-07T14:43:23.509+05:30  INFO 46404 --- [eurekaserver] [nio-8761-exec-1] c.n.e.registry.AbstractInstanceRegistry  : Registered instance EUREKACLIENT/Home:eurekaclient with status UP (replication=true)

Hit the URL http://localhost:8761/ in your web browser and you can see the Eureka Client application is registered with Eureka Server.

Eureka Client Application

Now hit the URL http://localhost:8080/ in your web browser and see the Rest Endpoint output.

Eureka Client Application Output

Spring Cloud - Eureka Client Consumer Example

In Spring Cloud - Creating Eureka Client chapter, we've seen that our Eureka Server created in Spring Cloud - Creating Eureka Server has got the registered client instances of the EurekaClient setup. We can now setup the Consumer which can ask the Eureka Server the address of the EurekaClient nodes.

Example - Eureka Registry Consumer

Let us add a controller which can get the information from the Eureka Registry. This controller will be added to our earlier Eureka Client itself. Let us create the following controller to the client.

EurekaClientInstanceController.java

The code for main Spring Boot application class file is as shown below −

package com.tutorialspoint.eurekaclient;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EurekaClientInstanceController {
	 @Autowired
	 private DiscoveryClient eurekaConsumer;
	   
	 @GetMapping("/customer_services")
	 public ResponseEntity<List<String>> getInstance() {
		 List<String> services = eurekaConsumer.getServices();
		 return new ResponseEntity<>(services, HttpStatus.OK);
	 }  	 
}

Note the annotation @DiscoveryClient which is what Spring framework provides to talk to the registry.

Run the Application and Verify the output

Run the Client Application as a Spring Boot App and you can get the output as shown below −


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

[32m :: Spring Boot :: [39m              [2m (v3.5.6)[0;39m

[2m2025-10-07T16:37:31.575+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [           main] [0;39m[36mc.t.e.EurekaclientApplication           [0;39m [2m:[0;39m Starting EurekaclientApplication using Java 21.0.6 with PID 32072 (D:\Projects\eurekaclient\target\classes started by mahes in D:\Projects\eurekaclient)
...
[2m2025-10-07T16:38:03.417+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [reshExecutor-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Registered Applications size is zero : true
[2m2025-10-07T16:38:03.417+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [reshExecutor-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Application version is -1: false
[2m2025-10-07T16:38:03.417+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [reshExecutor-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Getting all instance registry info from the eureka server
[2m2025-10-07T16:38:03.478+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [reshExecutor-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m The response status is 200
[2m2025-10-07T16:42:32.995+05:30[0;39m [32m INFO[0;39m [35m32072[0;39m [2m--- [eurekaclient] [rap-executor-%d] [0;39m[36mc.n.d.s.r.aws.ConfigClusterResolver     [0;39m [2m:[0;39m Resolving eureka endpoints via configuration

Verify the server output to have the client registered.

...
[2m2025-10-07T16:37:33.503+05:30[0;39m [32m INFO[0;39m [35m17048[0;39m [2m--- [eurekaserver] [nio-8761-exec-1] [0;39m[36mc.n.e.registry.AbstractInstanceRegistry [0;39m [2m:[0;39m Registered instance EUREKACLIENT/Home:eurekaclient with status UP (replication=false)
[2m2025-10-07T16:37:34.121+05:30[0;39m [32m INFO[0;39m [35m17048[0;39m [2m--- [eurekaserver] [nio-8761-exec-3] [0;39m[36mc.n.e.registry.AbstractInstanceRegistry [0;39m [2m:[0;39m Registered instance EUREKACLIENT/Home:eurekaclient with status UP (replication=true)

Now let's hit the url as http://localhost:8080/customer_services and you'll see the following output −

["eurekaclient"]

Spring Cloud - Eureka Server API

Eureka Server provides various APIs for the client instances or the services to talk to. A lot of these APIs are abstracted and can be used directly with @DiscoveryClient we defined and used earlier. Just to note, their HTTP counterparts also exist and can be useful for Non-Spring framework usage of Eureka.

We can use Server API to get the information about the client running EurekaClient can also be invoked via the browser using http://localhost:8761/eureka/apps/eurekaclient as can be seen here −

Example - Eureka Server API Usage via browser

Hit http://localhost:8761/eureka/apps/eurekaclient and verify the browser output as shown below −

<application>
   <name>EUREKACLIENT</name>
   <instance>
      <instanceId>Home:eurekaclient</instanceId>
      <hostName>Home</hostName>
      <app>EUREKACLIENT</app>
      <ipAddr>192.168.31.173</ipAddr>
      <status>UP</status>
      <overriddenstatus>UNKNOWN</overriddenstatus>
      <port enabled="true">8080</port>
      <securePort enabled="false">443</securePort>
      <countryId>1</countryId>
      <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
         <name>MyOwn</name>
      </dataCenterInfo>
      <leaseInfo>
         <renewalIntervalInSecs>30</renewalIntervalInSecs>
         <durationInSecs>90</durationInSecs>
         <registrationTimestamp>1759835254121</registrationTimestamp>
         <lastRenewalTimestamp>1759836004625</lastRenewalTimestamp>
         <evictionTimestamp>0</evictionTimestamp>
         <serviceUpTimestamp>1759835253497</serviceUpTimestamp>
      </leaseInfo>
      <metadata>
         <management.port>8080</management.port>
         <jmx.port>63088</jmx.port>
      </metadata>
      <homePageUrl>http://Home:8080/</homePageUrl>
      <statusPageUrl>http://Home:8080/actuator/info</statusPageUrl>
      <healthCheckUrl>http://Home:8080/actuator/health</healthCheckUrl>
      <vipAddress>eurekaclient</vipAddress>
      <secureVipAddress>eurekaclient</secureVipAddress>
      <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
      <lastUpdatedTimestamp>1759835254121</lastUpdatedTimestamp>
      <lastDirtyTimestamp>1759835253411</lastDirtyTimestamp>
      <actionType>ADDED</actionType>
   </instance>
</application>

Useful Server APIs

Few other useful APIs are −

Action API
Register a new service POST /eureka/apps/{appIdentifier}
Deregister the service DELTE /eureka/apps/{appIdentifier}
Information about the service GET /eureka/apps/{appIdentifier}
Information about the service instance GET /eureka/apps/{appIdentifier}/{instanceId}

More details about the programmatic API can be found here https://javadoc.io/doc/com.netflix.eureka/eureka-client/latest/index.html

Spring Cloud - Eureka High Availability

We have been using Eureka server in standalone mode. However, in a Production environment, we should ideally have more than one instance of the Eureka server running. This ensures that even if one machine goes down, the machine with another Eureka server keeps on running.

Let us try to setup Eureka server in high-availability mode. For our example, we will use two instances. For this, we will use the following application-ha.yml to start the Eureka server.

spring:
   application:
      name: eureka-server
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: ${eureka_other_server_url}

Points to note

  • We have parameterized the port so that we can start multiple instances using same the config file.

  • We have added address, again parameterized, to pass the Eureka server address.

  • We are naming the app as Eureka-Server.

Let us now recompile our Eureka server project using maven install command

.
[INFO] Scanning for projects...
[INFO] 
[INFO] [1m------------------< [0;36mcom.tutorialspoint:eurekaserver[0;1m >-------------------[m
[INFO] [1mBuilding eurekaserver 0.0.1-SNAPSHOT[m
[INFO]   from pom.xml
[INFO] [1m--------------------------------[ jar ]---------------------------------[m
[INFO] 
[INFO] [1m--- [0;32mresources:3.3.1:resources[m [1m(default-resources)[m @ [36meurekaserver[0;1m ---[m
[INFO] Copying 2 resources from src\main\resources to target\classes
[INFO] Copying 0 resource from src\main\resources to target\classes
[INFO] 
[INFO] [1m--- [0;32mcompiler:3.14.0:compile[m [1m(default-compile)[m @ [36meurekaserver[0;1m ---[m
[INFO] Recompiling the module because of [1madded or removed source files[m.
[INFO] Compiling 1 source file with javac [debug parameters release 21] to target\classes
[INFO] 
[INFO] [1m--- [0;32mresources:3.3.1:testResources[m [1m(default-testResources)[m @ [36meurekaserver[0;1m ---[m
[INFO] skip non existing resourceDirectory D:\Projects\eurekaserver\src\test\resources
[INFO] 
[INFO] [1m--- [0;32mcompiler:3.14.0:testCompile[m [1m(default-testCompile)[m @ [36meurekaserver[0;1m ---[m
[INFO] Recompiling the module because of [1mchanged dependency[m.
[INFO] Compiling 1 source file with javac [debug parameters release 21] to target\test-classes
[INFO] 
[INFO] [1m--- [0;32msurefire:3.5.4:test[m [1m(default-test)[m @ [36meurekaserver[0;1m ---[m
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.tutorialspoint.eurekaserver.[1mEurekaserverApplicationTests[m
17:10:29.063 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.tutorialspoint.eurekaserver.EurekaserverApplicationTests]: EurekaserverApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:10:29.171 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.tutorialspoint.eurekaserver.EurekaserverApplication for test class com.tutorialspoint.eurekaserver.EurekaserverApplicationTests

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.6)

2025-10-07T17:10:29.839+05:30  INFO 54892 --- [eurekaserver] [           main] c.t.e.EurekaserverApplicationTests       : Starting EurekaserverApplicationTests using Java 21.0.6 with PID 54892 (started by mahes in D:\Projects\eurekaserver)
2025-10-07T17:10:29.840+05:30  INFO 54892 --- [eurekaserver] [           main] c.t.e.EurekaserverApplicationTests       : No active profile set, falling back to 1 default profile: "default"
2025-10-07T17:10:30.916+05:30  INFO 54892 --- [eurekaserver] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=a28ee115-cbd0-38a4-9689-454190eff47b
2025-10-07T17:10:31.442+05:30  INFO 54892 --- [eurekaserver] [           main] o.s.v.b.OptionalValidatorFactoryBean     : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath.
2025-10-07T17:10:32.440+05:30  WARN 54892 --- [eurekaserver] [           main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-10-07T17:10:32.702+05:30  INFO 54892 --- [eurekaserver] [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2025-10-07T17:10:32.723+05:30  INFO 54892 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Initializing Eureka in region us-east-1
2025-10-07T17:10:32.724+05:30  INFO 54892 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Client configured to neither register nor query for data.
2025-10-07T17:10:32.728+05:30  INFO 54892 --- [eurekaserver] [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1759837232725 with initial instances count: 0
2025-10-07T17:10:32.813+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
2025-10-07T17:10:32.815+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Adding new peer nodes [http://localhost:8761/eureka/]
2025-10-07T17:10:33.009+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2025-10-07T17:10:33.009+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2025-10-07T17:10:33.010+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2025-10-07T17:10:33.010+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2025-10-07T17:10:33.086+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.eureka.cluster.PeerEurekaNodes       : Replica node URL:  http://localhost:8761/eureka/
2025-10-07T17:10:33.095+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: []
2025-10-07T17:10:33.095+05:30  INFO 54892 --- [eurekaserver] [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
2025-10-07T17:10:33.193+05:30  INFO 54892 --- [eurekaserver] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2025-10-07T17:10:33.254+05:30  INFO 54892 --- [eurekaserver] [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application EUREKASERVER with eureka with status UP
2025-10-07T17:10:33.269+05:30  INFO 54892 --- [eurekaserver] [           main] c.t.e.EurekaserverApplicationTests       : Started EurekaserverApplicationTests in 3.732 seconds (process running for 4.832)
2025-10-07T17:10:33.271+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
2025-10-07T17:10:33.273+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
2025-10-07T17:10:33.273+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
2025-10-07T17:10:33.273+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
2025-10-07T17:10:33.273+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
2025-10-07T17:10:33.275+05:30  INFO 54892 --- [eurekaserver] [      Thread-10] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
WARNING: A Java agent has been loaded dynamically (C:\Users\mahes\.m2\repository\net\bytebuddy\byte-buddy-agent\1.17.7\byte-buddy-agent-1.17.7.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
[INFO] [1;32mTests run: [0;1;32m1[m, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.114 s -- in com.tutorialspoint.eurekaserver.[1mEurekaserverApplicationTests[m
[INFO] 
[INFO] Results:
[INFO] 
[INFO] [1;32mTests run: 1, Failures: 0, Errors: 0, Skipped: 0[m
[INFO] 
[INFO] 
[INFO] [1m--- [0;32mjar:3.4.2:jar[m [1m(default-jar)[m @ [36meurekaserver[0;1m ---[m
[INFO] Building jar: D:\Projects\eurekaserver\target\eurekaserver-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] [1m--- [0;32mspring-boot:3.5.6:repackage[m [1m(repackage)[m @ [36meurekaserver[0;1m ---[m
[INFO] Replacing main artifact D:\Projects\eurekaserver\target\eurekaserver-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to D:\Projects\eurekaserver\target\eurekaserver-0.0.1-SNAPSHOT.jar.original
[INFO] 
[INFO] [1m--- [0;32minstall:3.1.4:install[m [1m(default-install)[m @ [36meurekaserver[0;1m ---[m
[INFO] Installing D:\Projects\eurekaserver\pom.xml to C:\Users\mahes\.m2\repository\com\tutorialspoint\eurekaserver\0.0.1-SNAPSHOT\eurekaserver-0.0.1-SNAPSHOT.pom
[INFO] Installing D:\Projects\eurekaserver\target\eurekaserver-0.0.1-SNAPSHOT.jar to C:\Users\mahes\.m2\repository\com\tutorialspoint\eurekaserver\0.0.1-SNAPSHOT\eurekaserver-0.0.1-SNAPSHOT.jar
[INFO] [1m------------------------------------------------------------------------[m
[INFO] [1;32mBUILD SUCCESS[m
[INFO] [1m------------------------------------------------------------------------[m
[INFO] Total time:  13.445 s
[INFO] Finished at: 2025-10-07T17:10:37+05:30
[INFO] [1m------------------------------------------------------------------------[m

For execution, we need to have two service instances running. To do that, let's open two shells and then execute the following command on one shell −

java -jar .\target\eurekaserver-0.0.1-SNAPSHOT.jar --app_port=8900 --eureka_other_server_url=http://localhost:8901/eureka' --spring.config.location=classpath:application-ha.yml

And execute the following on the other shell −

java -jar .\target\eurekaserver-0.0.1-SNAPSHOT.jar --app_port=8901 --eureka_other_server_url=http://localhost:8901/eureka' --spring.config.location=classpath:application-ha.yml

We can verify that the servers are up and running in high-availability mode by looking at the dashboard. For example, here is the dashboard on Eureka server 1 −

Dashboard on Eureka Server 1

And here is the dashboard of Eureka server 2 −

Dashboard of Eureka Server 2

So, as we see, we have two Eureka servers running and in sync. Even if one server goes down, the other server would keep functioning.

We can also update the service instance application to have addresses for both Eureka servers by having comma-separated server addresses.

spring:
   application:
      name: customer-service
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8900/eureka, http://localhost:8901/eureka

Spring Cloud - Eureka Zone Awareness

Eureka also supports the concept of zone awareness. Zone awareness as a concept is very useful when we have a cluster across different geographies. Say, we get an incoming request for a service and we need to choose the server which should service the request. Instead of sending and processing that request on a server which is located far, it is more fruitful to choose a server which is in the same zone. This is because, network bottleneck is very common in a distributed application and thus we should avoid it.

Let us now try to setup Eureka clients and make them Zone aware. For doing that, let us add application-za.yml

spring:
   application:
      name: customer-service
server:
   port: ${app_port}
eureka:
   instance:
      metadataMap:
         zone: ${zoneName}
   client:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

Let us now recompile our Eureka client project.

[INFO] Scanning for projects...
[INFO] 
[INFO] [1m------------------< [0;36mcom.tutorialspoint:eurekaclient[0;1m >-------------------[m
[INFO] [1mBuilding eurekaclient 0.0.1-SNAPSHOT[m
[INFO]   from pom.xml
[INFO] [1m--------------------------------[ jar ]---------------------------------[m
[INFO] 
[INFO] [1m--- [0;32mresources:3.3.1:resources[m [1m(default-resources)[m @ [36meurekaclient[0;1m ---[m
[INFO] Copying 2 resources from src\main\resources to target\classes
[INFO] Copying 0 resource from src\main\resources to target\classes
[INFO] 
[INFO] [1m--- [0;32mcompiler:3.14.0:compile[m [1m(default-compile)[m @ [36meurekaclient[0;1m ---[m
[INFO] Recompiling the module because of [1madded or removed source files[m.
[INFO] Compiling 2 source files with javac [debug parameters release 21] to target\classes
[INFO] 
[INFO] [1m--- [0;32mresources:3.3.1:testResources[m [1m(default-testResources)[m @ [36meurekaclient[0;1m ---[m
[INFO] skip non existing resourceDirectory D:\Projects\eurekaclient\src\test\resources
[INFO] 
[INFO] [1m--- [0;32mcompiler:3.14.0:testCompile[m [1m(default-testCompile)[m @ [36meurekaclient[0;1m ---[m
[INFO] Recompiling the module because of [1mchanged dependency[m.
[INFO] Compiling 1 source file with javac [debug parameters release 21] to target\test-classes
[INFO] 
[INFO] [1m--- [0;32msurefire:3.5.4:test[m [1m(default-test)[m @ [36meurekaclient[0;1m ---[m
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.tutorialspoint.eurekaclient.[1mEurekaclientApplicationTests[m
17:45:05.033 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.tutorialspoint.eurekaclient.EurekaclientApplicationTests]: EurekaclientApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:45:05.150 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.tutorialspoint.eurekaclient.EurekaclientApplication for test class com.tutorialspoint.eurekaclient.EurekaclientApplicationTests

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.6)

2025-10-07T17:45:05.775+05:30  INFO 32456 --- [eurekaclient] [           main] c.t.e.EurekaclientApplicationTests       : Starting EurekaclientApplicationTests using Java 21.0.6 with PID 32456 (started by mahes in D:\Projects\eurekaclient)
2025-10-07T17:45:05.776+05:30  INFO 32456 --- [eurekaclient] [           main] c.t.e.EurekaclientApplicationTests       : No active profile set, falling back to 1 default profile: "default"
2025-10-07T17:45:06.558+05:30  INFO 32456 --- [eurekaclient] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=61598357-3a65-347a-b2c3-05819b0e284d
2025-10-07T17:45:07.313+05:30  INFO 32456 --- [eurekaclient] [           main] DiscoveryClientOptionalArgsConfiguration : Eureka HTTP Client uses RestTemplate.
2025-10-07T17:45:07.348+05:30  WARN 32456 --- [eurekaclient] [           main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2025-10-07T17:45:07.377+05:30  INFO 32456 --- [eurekaclient] [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2025-10-07T17:45:07.425+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Initializing Eureka in region us-east-1
2025-10-07T17:45:07.425+05:30  INFO 32456 --- [eurekaclient] [           main] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2025-10-07T17:45:07.440+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Disable delta property : false
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Single vip registry refresh property : null
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Force full registry fetch : false
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Application is null : false
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Application version is -1: true
2025-10-07T17:45:07.452+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Getting all instance registry info from the eureka server
2025-10-07T17:45:08.103+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : The response status is 200
2025-10-07T17:45:08.106+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Starting heartbeat executor: renew interval is: 30
2025-10-07T17:45:08.108+05:30  INFO 32456 --- [eurekaclient] [           main] c.n.discovery.InstanceInfoReplicator     : InstanceInfoReplicator onDemand update allowed rate per min is 4
2025-10-07T17:45:08.108+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1759839308108 with initial instances count: 0
2025-10-07T17:45:08.108+05:30  INFO 32456 --- [eurekaclient] [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application EUREKACLIENT with eureka with status UP
2025-10-07T17:45:08.108+05:30  INFO 32456 --- [eurekaclient] [           main] com.netflix.discovery.DiscoveryClient    : Saw local status change event StatusChangeEvent [timestamp=1759839308108, current=UP, previous=STARTING]
2025-10-07T17:45:08.108+05:30  INFO 32456 --- [eurekaclient] [foReplicator-%d] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_EUREKACLIENT/Home:eurekaclient: registering service...
2025-10-07T17:45:08.122+05:30  INFO 32456 --- [eurekaclient] [           main] c.t.e.EurekaclientApplicationTests       : Started EurekaclientApplicationTests in 2.678 seconds (process running for 3.698)
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
2025-10-07T17:45:08.272+05:30  INFO 32456 --- [eurekaclient] [foReplicator-%d] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_EUREKACLIENT/Home:eurekaclient - registration status: 204
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
WARNING: A Java agent has been loaded dynamically (C:\Users\mahes\.m2\repository\net\bytebuddy\byte-buddy-agent\1.17.7\byte-buddy-agent-1.17.7.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
[INFO] [1;32mTests run: [0;1;32m1[m, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.790 s -- in com.tutorialspoint.eurekaclient.[1mEurekaclientApplicationTests[m
[INFO] 
[INFO] Results:
[INFO] 
[INFO] [1;32mTests run: 1, Failures: 0, Errors: 0, Skipped: 0[m
[INFO] 
[INFO] 
[INFO] [1m--- [0;32mjar:3.4.2:jar[m [1m(default-jar)[m @ [36meurekaclient[0;1m ---[m
[INFO] Building jar: D:\Projects\eurekaclient\target\eurekaclient-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] [1m--- [0;32mspring-boot:3.5.6:repackage[m [1m(repackage)[m @ [36meurekaclient[0;1m ---[m
[INFO] Replacing main artifact D:\Projects\eurekaclient\target\eurekaclient-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to D:\Projects\eurekaclient\target\eurekaclient-0.0.1-SNAPSHOT.jar.original
[INFO] 
[INFO] [1m--- [0;32minstall:3.1.4:install[m [1m(default-install)[m @ [36meurekaclient[0;1m ---[m
[INFO] Installing D:\Projects\eurekaclient\pom.xml to C:\Users\mahes\.m2\repository\com\tutorialspoint\eurekaclient\0.0.1-SNAPSHOT\eurekaclient-0.0.1-SNAPSHOT.pom
[INFO] Installing D:\Projects\eurekaclient\target\eurekaclient-0.0.1-SNAPSHOT.jar to C:\Users\mahes\.m2\repository\com\tutorialspoint\eurekaclient\0.0.1-SNAPSHOT\eurekaclient-0.0.1-SNAPSHOT.jar
[INFO] [1m------------------------------------------------------------------------[m
[INFO] [1;32mBUILD SUCCESS[m
[INFO] [1m------------------------------------------------------------------------[m
[INFO] Total time:  9.863 s
[INFO] Finished at: 2025-10-07T17:45:11+05:30
[INFO] [1m------------------------------------------------------------------------[m

For execution, we will have two service instances running. To do that, let's open two shells and then execute the following command on one shell −

java -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --app_port=8080 --zone_name=USA --spring.config.location=classpath:application-za.yml

And execute the following on the other shell −

java -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --app_port=8081 --zone_name=EU --spring.config.location=classpath:application-za.yml

We can go back to the dashboard to verify that the Eureka Server registers the zone of the services. As seen in the following image, we have two availability zones instead of 1, which we have been seeing till now.

Eureka Server

Now, any client can look at the zone it is present in. Say the client is located in USA, it would prefer the service instance of USA. And it can get the zone information from the Eureka Server.

Synchronous Communication with Feign Client

Introduction

In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. In this section, we will look at how services can communicate by synchronous API calls.

Although this sounds simple, as part of making API calls, we need to take care of the following −

  • Finding address of the callee − The caller service needs to know the address of the service which it wants to call.

  • Load balancing − The caller service can do some intelligent load balancing to spread the load across callee services.

  • Zone awareness − The caller service should preferably call the services which are in the same zone for quick responses.

Netflix Feign and Spring RestTemplate (along with Ribbon) are two well-known HTTP clients used for making synchronous API calls. In this tutorial, we will use Feign Client.

Feign Dependency Setting

In order to use Feign Client API, we need to update the pom.xml of the client service with the following dependency −

<dependencies>
      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
</dependencies>

Enable Feign Clients

And then, annotate our Spring application class with the correct annotation, i.e., @EnableDiscoveryClient and @EnableFeignCLient

package com.tutorialspoint;

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

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class RestaurantService{
   public static void main(String[] args) {
      SpringApplication.run(RestaurantService.class, args);
   }
}

Points to note in the above code −

  • @ EnableDiscoveryClient − This is the same annotation which we use for reading/writing to the Eureka server.

  • @EnableFeignCLient − This annotation scans our packages for enabled feign client in our code and initializes it accordingly.

Once done, now let us look briefly at Feign Interfaces which we need to define the Feign clients.

Using Feign Interfaces for API calls

Feign client can be simply setup by defining the API calls in an interface which can be used in Feign to construct the boilerplate code required to call the APIs. For example, consider we have two services −

  • Service A − Caller service which uses the Feign Client.

  • Service B − Callee service whose API would be called by the above Feign client

The caller service, i.e., service A in this case needs to create an interface for the API which it intends to call, i.e., service B.

package com.tutorialspoint;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(name = "service-B")
public interface ServiceBInterface {
   @RequestMapping("/objects/{id}", method=GET)
   public ObjectOfServiceB getObjectById(@PathVariable("id") Long id);
   @RequestMapping("/objects/", method=POST)
   public void postInfo(ObjectOfServiceB b);
   @RequestMapping("/objects/{id}", method=PUT)
   public void postInfo((@PathVariable("id") Long id, ObjectOfBServiceB b);
}

Points to note

  • The @FeignClient annotates the interfaces which will be initialized by Spring Feign and can be used by rest of the code.

  • Note that the FeignClient annotation needs to contain the name of the service, this is used to discover the service address, i.e., of service B from Eureka or other discovery platforms.

  • We can then define all the API function name which we plan to call from service A. This can be general HTTP calls with GET, POST, PUT, etc., verbs.

Once this is done, service A can simply use the following code to call the APIs of service B −

@Autowired
ServiceBInterface serviceB
.
.
.
ObjectOfServiceB object = serviceB. getObjectById(5);

Spring Cloud - Feign Client with Eureka

Let us say we want to find restaurants which are in the same city as that of the customer. We will use the following services −

  • Customer Service − Has all the customer information. We had defined this in Eureka Client section earlier.

  • Eureka Discovery Server − Has information about the above services. We had defined this in the Eureka Server section earlier.

  • Restaurant Service − New service which we will define which has all the restaurant information.

We're using client service created in Spring Cloud - Creating Eureka Client chapter,and Eureka Server created in Spring Cloud - Creating Eureka Server.

Let us first add a basic controller to our Customer service −

RestaurantCustomerInstancesController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantCustomerInstancesController {
   static HashMap<Long, Customer> mockCustomerData = new HashMap<>();
   static{
      mockCustomerData.put(1L, new Customer(1, "Jane", "DC"));
      mockCustomerData.put(2L, new Customer(2, "John", "SFO"));
      mockCustomerData.put(3L, new Customer(3, "Kate", "NY"));
   }
   @RequestMapping("/customer/{id}")
   public Customer getCustomerInfo(@PathVariable("id") Long id) {
      return mockCustomerData.get(id);
   }
}

We will also define a Customer.java POJO for the above controller.

Customer.java

package com.tutorialspoint.eurekaclient;

public class Customer {
   private long id;
   private String name;
   private String city;
   public Customer() {}
   public Customer(long id, String name, String city) {
      super();
      this.id = id;
      this.name = name;
      this.city = city;
   }
   public long getId() {
      return id;
   }
   public void setId(long id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getCity() {
      return city;
   }
   public void setCity(String city) {
      this.city = city;
   }
}

Now let us move to define the Feign client which the Restaurant service will use to get the customer city.

CustomerService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "restaurant-service")
public interface CustomerService {
   @RequestMapping("/customer/{id}")
   public Customer getCustomerById(@PathVariable("id") Long id);
}

The Feign client contains the name of the service and the API call we plan to use in the Restaurant service.

Finally, let us define a controller in the Restaurant service which would use the above interface.

RestaurantController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantController {
   @Autowired
   CustomerService customerService;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
   }
   @RequestMapping("/restaurant/customer/{id}")
   public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long id) {
      String customerCity = customerService.getCustomerById(id).getCity();
      return mockRestaurantData.entrySet().stream().filter(
         entry -> entry.getValue().getCity().equals(customerCity))
           .map(entry -> entry.getValue())
           .collect(Collectors.toList());
   }
}

The most important line here is the following −

customerService.getCustomerById(id)

which is where the magic of API calling by Feign client we defined earlier happens.

Let us also define the Restaurant POJO

package com.tutorialspoint.eurekaclient;

public class Restaurant {
   private long id;
   private String name;
   private String city;
   public Restaurant(long id, String name, String city) {
      super();
      this.id = id;
      this.name = name;
      this.city = city;
   }
   public long getId() {
      return id;
   }
   public void setId(long id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getCity() {
      return city;
   }
   public void setCity(String city) {
      this.city = city;
   }
}

Once this is defined, let us create a simple JAR file with the following application.yml file −

spring:
   application:
      name: restaurant-service
server:
   port: 8080
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

Spring Cloud - Test Feign Client

To test the Feign Client, run the project updated in Spring Cloud - Feign Client with Eureka as a spring boot app. It will run the service and register as restaurant-service with eureka.

Output - Eureka Client


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

[32m :: Spring Boot :: [39m              [2m (v3.5.6)[0;39m
...
[2m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [           main] [0;39m[36mo.s.c.n.e.s.EurekaServiceRegistry       [0;39m [2m:[0;39m Registering application RESTAURANT-SERVICE with eureka with status UP
[2m2025-10-08T11:19:51.001+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [           main] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Saw local status change event StatusChangeEvent [timestamp=1759902591001, current=UP, previous=STARTING]
[2m2025-10-08T11:19:51.003+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [foReplicator-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m DiscoveryClient_RESTAURANT-SERVICE/Home:restaurant-service:8080: registering service...
[2m2025-10-08T11:19:51.027+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [           main] [0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat started on port 8080 (http) with context path '/'
[2m2025-10-08T11:19:51.028+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [           main] [0;39m[36m.s.c.n.e.s.EurekaAutoServiceRegistration[0;39m [2m:[0;39m Updating port to 8080
[2m2025-10-08T11:19:51.043+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [           main] [0;39m[36mc.t.e.EurekaclientApplication           [0;39m [2m:[0;39m Started EurekaclientApplication in 2.427 seconds (process running for 3.147)
[2m2025-10-08T11:19:51.049+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [foReplicator-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m DiscoveryClient_RESTAURANT-SERVICE/Home:restaurant-service:8080 - registration status: 204
[2m2025-10-08T11:20:12.672+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [nio-8080-exec-1] [0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/]      [0;39m [2m:[0;39m Initializing Spring DispatcherServlet 'dispatcherServlet'
[2m2025-10-08T11:20:12.672+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [nio-8080-exec-1] [0;39m[36mo.s.web.servlet.DispatcherServlet       [0;39m [2m:[0;39m Initializing Servlet 'dispatcherServlet'
[2m2025-10-08T11:20:12.673+05:30[0;39m [32m INFO[0;39m [35m44352[0;39m [2m--- [restaurant-service] [nio-8080-exec-1] [0;39m[36mo.s.web.servlet.DispatcherServlet       [0;39m [2m:[0;39m Completed initialization in 1 ms

Output - Eureka Server


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

...
[2m2025-10-08T11:15:21.491+05:30[0;39m [32m INFO[0;39m [35m41940[0;39m [2m--- [           main] [0;39m[36mo.apache.catalina.core.StandardEngine   [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.46]
[2m2025-10-08T11:15:21.537+05:30[0;39m [32m INFO[0;39m [35m41940[0;39m [2m--- [           main] [0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/]      [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
[2m2025-10-08T11:15:21.538+05:30[0;39m [32m INFO[0;39m [35m41940[0;39m [2m--- [           main] [0;39m[36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 1006 ms
..
[2m2025-10-08T11:19:51.047+05:30[0;39m [32m INFO[0;39m [35m41940[0;39m [2m--- [nio-8761-exec-6] [0;39m[36mc.n.e.registry.AbstractInstanceRegistry [0;39m [2m:[0;39m Registered instance RESTAURANT-SERVICE/Home:restaurant-service:8080 with status UP (replication=false)
[2m2025-10-08T11:19:51.577+05:30[0;39m [32m INFO[0;39m [35m41940[0;39m [2m--- [nio-8761-exec-7] [0;39m[36mc.n.e.registry.AbstractInstanceRegistry [0;39m [2m:[0;39m Registered instance RESTAURANT-SERVICE/Home:restaurant-service:8080 with status UP (replication=true)

Testing Services

In all, we have the following items running −

  • Standalone Eureka server

  • Customer service

  • Restaurant service

We can confirm that the above are working from the dashboard on http://localhost:8900/

Now, let us try to find all the restaurants which can serve to Jane who is placed in DC.

For this, first let us hit the customer service for the same: http://localhost:8080/customer/1

{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

And then, make a call to the Restaurant Service via Feign Client: http://localhost:8080/restaurant/customer/1

[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

As we see, Jane can be served by 2 restaurants which are in DC area.

To conclude, as we see, without writing any boilerplate code and even specifying the address of the service, we can make HTTP calls to the services.

Spring Cloud - Gateway

Introduction

In a distributed environment, services need to communicate with each other. However, this is interservice communication. We also have use-cases where a client outside our domain wants to hit our services for the API. So, either we can expose the address of all our microservices which can be called by clients OR we can create a Service Gateway which routes the request to various microservices and responds to the clients.

Creating a Gateway is much better approach here. There are two major advantages −

  • The security for each individual services does not need to maintained.

  • And, cross-cutting concerns, for example, addition of meta-information can be handled at a single place.

Netflix Zuul and Spring Cloud Gateway are two well-known Cloud Gateways which are used to handle such situations. In this tutorial, we will use Spring Cloud Gateway.

Spring Cloud Gateway Dependency Setting

Let us use the case of Restaurant which we have been using. Let us add a new service (gateway) in front of our two services, i.e., Restaurant services and Customer Service. First, let us update the pom.xml of the service with the following dependency −

<dependencies>
   <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>
   <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-gateway</artifactId>
   </dependency>
</dependencies>

And then, annotate our Spring application class with the correct annotation, i.e., @EnableDiscoveryClient.

package com.tutorialspoint;

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

@SpringBootApplication
@EnableDiscoveryClient
public class RestaurantGatewayService{
   public static void main(String[] args) {
      SpringApplication.run(RestaurantGatewayService.class, args);
   }
}

We are annotating with @EnableDiscoveryClient because we want to use Eureka service discovery to get the list of hosts which are serving a particular use-case

Dynamic Routing with Gateway

The Spring Cloud Gateway has three important parts to it. Those are −

  • Route − These are the building blocks of the gateway which contain URL to which request is to be forwarded to and the predicates and filters that are applied on the incoming requests.

  • Predicate − These are the set of criteria which should match for the incoming requests to be forwarded to internal microservices. For example, a path predicate will forward the request only if the incoming URL contains that path.

  • Filters − These act as the place where you can modify the incoming requests before sending the requests to the internal microservices or before responding back to the client.

Spring Cloud - Creating Gateway Application

The Gateway Server is bundled with Spring Cloud dependency. You can download the Spring Boot project from Spring Initializer page https://start.spring.io/ and choose the Zuul Server dependency.

Creating Gateway Server Application

You will have to add the Spring Cloud Starter Gateway and dependency in our build configuration file.

Maven users will have to add the following dependency in your pom.xml file −

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Example - Gateway Application

Once you open the downloaded spring boot project as mentioned above, we need to update source code as below −

We are annotating with @EnableDiscoveryClient because we want to use Eureka service discovery.

GatewayApplication.java

package com.tutorialspoint.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

   public static void main(String[] args) {
      SpringApplication.run(GatewayApplication.class, args);
   }	
	
   @Bean
   public RouteLocator myRoutes(RouteLocatorBuilder builder) {
      return builder.routes()
         .route(p -> p
            .path("/customer/**")           
            .uri("http://localhost:8080/customer-service"))
         .route(p -> p
            .path("/restaurant/**")           
            .uri("http://localhost:8080/restaurant-service"))
            .build();
   }
}

application.properties

Let us write a simple configuration for the Gateway for our Restaurant and Customer service.

spring.application.name=restaurant-gateway-service

server.port=8084
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

Points to note about the above configuration −

  • We have enabled the discovery.locator to ensure that the gateway can read from the Eureka server.

  • We have used Path predicated here to route the request. What this means is that any request which begins with /customer would be routed to Customer Service and for /restaurant, we will forward that request to Restaurant Service.

Now our Gateway application is ready for testing which will perform in next chapter.

Spring Cloud - Testing Gateway Application

Now in order to test gateway application created in Spring Cloud - Creating Gateway Application chapter. We required the following application running prior to gateway application.

Testing Gateway application

Now run the Gateway application as Run as > Spring Boot App in Eclipse and it will show the following output in console.


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

[32m :: Spring Boot :: [39m              [2m (v3.5.6)[0;39m

[2m2025-10-09T10:21:43.941+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mc.t.gateway.GatewayApplication          [0;39m [2m:[0;39m Starting GatewayApplication using Java 21.0.6 with PID 40680 (D:\Projects\gateway\target\classes started by mahes in D:\Projects\gateway)
[2m2025-10-09T10:21:43.943+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mc.t.gateway.GatewayApplication          [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
[2m2025-10-09T10:21:44.687+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mo.s.cloud.context.scope.GenericScope    [0;39m [2m:[0;39m BeanFactory id=dc957f04-6ca7-30aa-8917-3f2c108d81b3
[2m2025-10-09T10:21:45.312+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mo.s.c.g.r.RouteDefinitionRouteLocator   [0;39m [2m:[0;39m Loaded RoutePredicateFactory [After]
...
[2m2025-10-09T10:21:45.518+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mDiscoveryClientOptionalArgsConfiguration[0;39m [2m:[0;39m Eureka HTTP Client uses RestTemplate.
[2m2025-10-09T10:21:45.547+05:30[0;39m [33m WARN[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] 
[2m2025-10-09T10:21:45.958+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Discovery Client initialized at timestamp 1759985505957 with initial instances count: 1
[2m2025-10-09T10:21:45.960+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mo.s.c.n.e.s.EurekaServiceRegistry       [0;39m [2m:[0;39m Registering application RESTAURANT-GATEWAY-SERVICE with eureka with status UP
[2m2025-10-09T10:21:45.960+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [           main] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m Saw local status change event StatusChangeEvent [timestamp=1759985505960, current=UP, previous=STARTING]
[2m2025-10-09T10:21:45.962+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [foReplicator-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m DiscoveryClient_RESTAURANT-GATEWAY-SERVICE/Home:restaurant-gateway-service:8084: registering service...
[2m2025-10-09T10:21:46.002+05:30[0;39m [32m INFO[0;39m [35m40680[0;39m [2m--- [restaurant-gateway-service] [foReplicator-%d] [0;39m[36mcom.netflix.discovery.DiscoveryClient   [0;39m [2m:[0;39m DiscoveryClient_RESTAURANT-GATEWAY-SERVICE/Home:restaurant-gateway-service:8084 - registration status: 204

Once this is done, we have our Gateway ready to be tested on port 8084. Lets first hit http://localhost:8084/customer/1 and we see the request is correctly routed to Customer Service and we get the following output −

{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

And now, hit our restaurant API, i.e., http://localhost:8084/restaurant/customer/1 and we get the following output −

[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

This means that both the calls were correctly routed to the respective services.

Predicates & Filters Request

We had used Path predicate in our above example. Here are a few other important predicates −

Predicate Description
Cookie predicate (input: name and regex) Compares the cookie with the name to the regexp
Header predicate (input: name and regex) Compares the header with the name to the regexp
Host predicate (input: name and regex) Compares the name of the incoming to the regexp
Weight Predicate (input: Group name and the weight) Weight Predicate (input: Group name and the weight)

Filters are used to add/remove data from the request before sending the data to the downstream service or before sending the response back to the client.

Following are a few important filters for adding metadata.

Filter Description
Add request header filter (input: header and the value) Add a header and the value before forwarding the request downstream.
Add response header filter (input: header and the value) Add a header and the value before forwarding the request upstream that is to the client.
Redirect filter (input: status and URL) Adds a redirect header along with the URL before passing over o the downstream host.
ReWritePath (input: regexp and replacement) This is responsible for rewriting the path by replacing the regexp matched string with the input replacement.

The exhaustive list for filters and predicates is present at https://cloud.spring.io/spring-cloudgateway/reference/html/#the-rewritepath-gatewayfilter-factory

Spring Cloud - Monitoring Gateway Application

For monitoring of the Gateway or for accessing various routes, predicates, etc., we can enable the actuator in the project. For doing that, let us first update the pom.xml to contain the actuator as a dependency.

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

For monitoring, we're enabling the actuator. So, here is how it would look like −

spring.application.name=restaurant-gateway-service

server.port=8084
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

management.endpoint.gateway.access=UNRESTRICTED
management.endpoints.web.exposure.include=gateway

Now, to list all the routes, we can hit: http://localhost:8084/actuator/gateway/routes

[
  {
    "predicate": "Paths: [/customer/**], match trailing slash: true",
    "route_id": "b8f0a6a6-eb31-469c-acf0-2cb9f51f7aa1",
    "filters": [],
    "uri": "http://localhost:8080/customer-service",
    "order": 0
  },
  {
    "predicate": "Paths: [/restaurant/**], match trailing slash: true",
    "route_id": "18b0ba68-3533-412c-b138-bc61b5b48e18",
    "filters": [],
    "uri": "http://localhost:8080/restaurant-service",
    "order": 0
  }
]

Other important APIs for monitoring −

API Description
GET /actuator/gateway/routes/{id} Get information about a particular route
POST /gateway/routes/{id_to_be assigned} Add a new route to the Gateway
DELETE /gateway/routes/{id} Remove the route from Gateway
POST /gateway/refresh Remove all the cache entries

Spring Cloud - Load Balancer

Introduction

In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. Now, when a service communicates synchronously, it is better for those services to load balance the request among workers so that a single worker does not get overwhelmed. There are two ways to load balance the request

  • Server-side Load Balancing − The workers are fronted by a software which distributes the incoming requests among the workers.

  • Client-side Load Balancing − The caller service themselves distribute the requests among the workers. The benefit of client-side load balancing is that we do not need to have a separate component in the form of a load balancer. We do not need to have high availability of the load balancer etc. Also, we avoid the need to have extra hop from client to LB to worker to get the request fulfilled. So, we save on latency, infrastructure, and maintenance cost.

Spring Cloud load balancer (SLB) and Netflix Ribbon are two well-known client-side load balancer which are used to handle such situation. In this tutorial, we will use Spring Cloud Load Balancer.

Load Balancer Dependency Setting

Lets use the case of restaurant we have been using in the previous chapters. Let us reuse the Restaurant Service which has all the information about the restaurant. Note that we will use Feign Client with our Load balancer.

First, let us update the pom.xml of the service with following dependency −

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Our load balancer would be using Eureka as a discovery client to get information about the worker instances. For that, we will have to use @EnableDiscoveryClient annotation.

RestaurantService.java

package com.tutorialspoint.eurekaclient;

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

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class RestaurantService{
   public static void main(String[] args) {
      SpringApplication.run(RestaurantService.class, args);
   }
}

Using Spring Load Balancer with Feign

@FeignClient annotation that we had used in Feign actually packs in a default setup for the load balancer client which round-robins our request. Let us test this out. Here is the same Feign client from our Feign section earlier.

CustomerService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "customer-service")
public interface CustomerService {
   @RequestMapping("/customer/{id}")
   public Customer getCustomerById(@PathVariable("id") Long id);
}

And here is the controller which we will use. Again, this has not been changed.

RestaurantController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantController {
   @Autowired
   CustomerService customerService;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
      mockRestaurantData.put(4L, new Restaurant(4, "Pizeeria", "NY"));
   }
   @RequestMapping("/restaurant/customer/{id}")
   public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long
id) {
      System.out.println("Got request for customer with id: " + id);
      String customerCity = customerService.getCustomerById(id).getCity();
      return mockRestaurantData.entrySet().stream().filter(
         entry -> entry.getValue().getCity().equals(customerCity))
         .map(entry -> entry.getValue())
         .collect(Collectors.toList());
   }
}

Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −

  • Start the Eureka Server.

  • Start two instances of the Customer Service.

  • Start a Restaurant Service which internally calls Customer Service and uses the Spring Cloud Load balancer

  • Make four API calls to the Restaurant Service. Ideally, two requests would be served by each customer service.

Assuming, we have started the Eureka server and the Customer service instances, let us now compile the Restaurant Service code and run as spring boot app −

Now, let us find restaurants for Jane who is based in DC by hitting the following API http://localhost:8080/restaurant/customer/1 and let us hit the same API three times again. You would notice from the logs of the Customer Service that both the instances serve 2 requests. Each of the Customer Service shell would print the following −

Querying customer for id with: 1
Querying customer for id with: 1

This effectively means that the request was round robin-ed.

Configuring Spring Load Balancer

We can configure the load balancer to change the type of algorithm or we can also provide customized algorithm. Let us see how to tweak our load balancer to prefer the same client for the request.

For that purpose, let us update our Feign Client to contain load balancer definition.

CustomerService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "customer-service")
@LoadBalancerClient(name = "customer-service",
configuration=LoadBalancerConfiguration.class)
public interface CustomerService {
   @RequestMapping("/customer/{id}")
   public Customer getCustomerById(@PathVariable("id") Long id);
}

If you notice, we have added the @LoadBalancerClient annotation which specifies the type of load balancer which would be used for this Feign client. We can create a configuration class for the load balancer and pass on the class to the annotation itself. Now let us define LoadBalancerConfiguratio.java

LoadBalancerConfiguration.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoadBalancerConfiguration {
   @Bean
   public ServiceInstanceListSupplier
discoveryClientServiceInstanceListSupplier(
         ConfigurableApplicationContext context) {
      System.out.println("Configuring Load balancer to prefer same instance");
      return ServiceInstanceListSupplier.builder()
               .withBlockingDiscoveryClient()
               .withSameInstancePreference()
               .build(context);
      }
}

Now, as you see, we have setup our client-side load balancing to prefer the same instance every time. Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −

  • Start the Eureka Server.

  • Start two instances of the Customer Service.

  • Start a Restaurant Service which internally calls Customer Service and uses the Spring Cloud Load balancer

  • Make 4 API calls to the Restaurant Service. Ideally, all four requests would be served by the same customer service.

Assuming, we have started the Eureka server and the Customer service instances, let us now compile the Restaurant Service code and run as spring boot app −

Now, let us find restaurants for Jane who is based in DC by hitting the following API http://localhost:8080/restaurant/customer/1 and let us hit the same API three times again. You would notice from the logs of the Customer Service that a single instance serves all 4 requests −

Querying customer for id with: 1
Querying customer for id with: 1
Querying customer for id with: 1
Querying customer for id with: 1

This effectively means that the requests have preferred the same customer service agent.

On similar lines, we can have various other load balancing algorithms to use sticky sessions, hintbased load balancing, zone preference load balancing, and so on.

Spring Cloud - Circuit Breaker using Hystrix

Introduction

In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. When services communicate synchronously, there can be multiple reasons where things can break. For example −

  • Callee service unavailable − The service which is being called is down for some reason, for example − bug, deployment, etc.

  • Callee service taking time to respond − The service which is being called can be slow due to high load or resource consumption or it is in the middle of initializing the services.

In either of the cases, it is waste of time and network resources for the caller to wait for the callee to respond. It makes more sense for the service to back off and give calls to the callee service after some time or share default response.

Netflix Hystrix, Resilince4j are two well-known circuit breakers which are used to handle such situations. In this tutorial, we will use Hystrix.

Note* − From Spring Boot 3, Hytrix is deprecated and not supported. We're using older version to showcase the functionality.

Hystrix Dependency Setting

Let us use the case of Restaurant that we have been using earlier. Let us add hystrix dependency to our Restaurant Services which call the Customer Service. First, let us update the pom.xml of the service with the following dependency −

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
   <version>2.7.0.RELEASE</version>
</dependency>

And then, annotate our Spring application class with the correct annotation, i.e., @EnableHystrix

RestaurantService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableHystrix
public class RestaurantService{
   public static void main(String[] args) {
      SpringApplication.run(RestaurantService.class, args);
   }
}

Points to Note

  • @ EnableDiscoveryClient and @EnableFeignCLient − We have already looked at these annotations in the previous chapter.

  • @EnableHystrix − This annotation scans our packages and looks out for methods which are using @HystrixCommand annotation.

Hystrix Command Annotation

Once done, we will reuse the Feign client which we had defined for our customer service class earlier in the Restaurant service, no changes here −

CustomerService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "customer-service")
public interface CustomerService {
   @RequestMapping("/customer/{id}")
   public Customer getCustomerById(@PathVariable("id") Long id);
}

Now, let us define the service implementation class here which would use the Feign client. This would be a simple wrapper around the feign client.

CustomerServiceImpl.java

package com.tutorialspoint.eurekaclient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@Service
public class CustomerServiceImpl implements CustomerService {
   @Autowired
   CustomerService customerService;
   @HystrixCommand(fallbackMethod="defaultCustomerWithNYCity")
   public Customer getCustomerById(Long id) {
      return customerService.getCustomerById(id);
   }
   // assume customer resides in NY city
   public Customer defaultCustomerWithNYCity(Long id) {
      return new Customer(id, null, "NY");
   }
}

Now, let us understand couple of points from the above code −

  • HystrixCommand annotation − This is responsible for wrapping the function call that is getCustomerById and provide a proxy around it. The proxy then gives various hooks through which we can control our call to the customer service. For example, timing out the request,pooling of request, providing a fallback method, etc.

  • Fallback method − We can specify the method we want to call when Hystrix determines that something is wrong with the callee. This method needs to have same signature as the method which is annotated. In our case, we have decided to provide the data back to our controller for the NY city.

Couple of useful options this annotation provides −

  • Error threshold percent − Percentage of request allowed to fail before the circuit is tripped, that is, fallback methods are called. This can be controlled by using cicutiBreaker.errorThresholdPercentage

  • Giving up on the network request after timeout − If the callee service, in our case Customer service, is slow, we can set the timeout after which we will drop the request and move to fallback method. This is controlled by setting execution.isolation.thread.timeoutInMilliseconds

And lastly, here is our controller which we call the CustomerServiceImpl

CustomerServiceImpl.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantController {
   @Autowired
   CustomerServiceImpl customerService;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
      mockRestaurantData.put(3L, new Restaurant(4, "Pizeeria", "NY"));
   }
   @RequestMapping("/restaurant/customer/{id}")
   public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long
id)
{
   System.out.println("Got request for customer with id: " + id);
   String customerCity = customerService.getCustomerById(id).getCity();
   return mockRestaurantData.entrySet().stream().filter(
      entry -> entry.getValue().getCity().equals(customerCity))
      .map(entry -> entry.getValue())
      .collect(Collectors.toList());
   }
}

Circuit Tripping/Opening

Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −

  • Start the Eureka Server

  • Start the Customer Service

  • Start the Restaurant Service which will internally call Customer Service.

  • Make an API call to Restaurant Service

  • Shut down the Customer Service

  • Make an API call to Restaurant Service. Given that Customer Service is down, it would cause failure and ultimately, the fallback method would be called.

Let us now compile the Restaurant Service code and execute with the following command −

java --app_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar

Also, start the Customer Service and the Eureka server. Note that there are no changes in these services and they remain same as seen in the previous chapters.

Now, let us try to find restaurant for Jane who is based in DC.

{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

For doing that, we will hit the following URL: http://localhost:8082/restaurant/customer/1

[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

So, nothing new here, we got the restaurants which are in DC. Now, let's move to the interesting part which is shutting down the Customer service. You can do that either by hitting Ctrl+C or simply killing the shell.

Now let us hit the same URL again − http://localhost:8082/restaurant/customer/1

{
   "id": 4,
   "name": "Pizzeria",
   "city": "NY"
}

As is visible from the output, we have got the restaurants from NY, although our customer is from DC.This is because our fallback method returned a dummy customer who is situated in NY. Although, not useful, the above example displays that the fallback was called as expected.

Integrating Caching with Hystrix

To make the above method more useful, we can integrate caching when using Hystrix. This can be a useful pattern to provide better answers when the underlying service is not available.

First, let us create a cached version of the service.

CustomerServiceCachedFallback.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service
public class CustomerServiceCachedFallback implements CustomerService {
   Map<Long, Customer> cachedCustomer = new HashMap<>();
   @Autowired
   CustomerService customerService;
   @HystrixCommand(fallbackMethod="defaultToCachedData")
   public Customer getCustomerById(Long id) {
      Customer customer = customerService.getCustomerById(id);
      // cache value for future reference
      cachedCustomer.put(customer.getId(), customer);
      return customer;
   }
   // get customer data from local cache
   public Customer defaultToCachedData(Long id) {
      return cachedCustomer.get(id);
   }
}

We are using hashMap as the storage to cache the data. This for developmental purpose. In Production environment, we may want to use better caching solutions, for example, Redis, Hazelcast, etc.

Now, we just need to update one line in the controller to use the above service −

@RestController
class RestaurantController {
   @Autowired
   CustomerServiceCachedFallback customerService;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   
}

We will follow the same steps as above −

  • Start the Eureka Server.

  • Start the Customer Service.

  • Start the Restaurant Service which internally call Customer Service.

  • Make an API call to the Restaurant Service.

  • Shut down the Customer Service.

  • Make an API call to the Restaurant Service. Given that Customer Service is down but the data is cached, we will get a valid set of data.

Now, let us follow the same process till step 3.

Now hit the URL: http://localhost:8082/restaurant/customer/1

[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

So, nothing new here, we got the restaurants which are in DC. Now, let us move to the interesting part which is shutting down the Customer service. You can do that either by hitting Ctrl+C or simply killing the shell.

Now let us hit the same URL again − http://localhost:8082/restaurant/customer/1

[
   {
      "id": 1,
      "name": "Pandas",
      "city": "DC"
   },
   {
      "id": 3,
      "name": "Little Italy",
      "city": "DC"
   }
]

As is visible from the output, we have got the restaurants from DC which is what we expect as our customer is from DC. This is because our fallback method returned a cached customer data.

Integrating Feign with Hystrix

We saw how to use @HystrixCommand annotation to trip the circuit and provide a fallback. But we had to additionally define a Service class to wrap our Hystrix client. However, we can also achieve the same by simply passing correct arguments to Feign client. Let us try to do that. For that, first update our Feign client for CustomerService by adding a fallback class.

CustomerService.java

package com.tutorialspoint.eurekaclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(name = "customer-service", fallback = FallBackHystrix.class)
public interface CustomerService {
   @RequestMapping("/customer/{id}")
   public Customer getCustomerById(@PathVariable("id") Long id);
}

Now, let us add the fallback class for the Feign client which will be called when the Hystrix circuit is tripped.

FallBackHystrix.java

package com.tutorialspoint.eurekaclient;

import org.springframework.stereotype.Component;
@Component
public class FallBackHystrix implements CustomerService{
   @Override
   public Customer getCustomerById(Long id) {
      System.out.println("Fallback called....");
      return new Customer(0, "Temp", "NY");
   }
}

Lastly, we also need to create the application-circuit.yml to enable hystrix.

spring:
   application:
      name: restaurant-service
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8900/eureka
feign:
   circuitbreaker:
      enabled: true

Now, that we have the setup ready, let us test this out. We will follow these steps −

  • Start the Eureka Server.

  • We do not start the Customer Service.

  • Start the Restaurant Service which will internally call Customer Service.

  • Make an API call to Restaurant Service. Given that Customer Service is down, we will notice the fallback.

Assuming 1st step is already done, let's move to step 3. Let us compile the code and execute the following command −

java --app_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-circuit.yml

Let us now try to hit − http://localhost:8082/restaurant/customer/1

As we have not started Customer Service, fallback would be called and the fallback sends over NY as the city, which is why, we see NY restaurants in the following output.

{
   "id": 4,
   "name": "Pizzeria",
   "city": "NY"
}

Also, to confirm, in the logs, we would see −

.
2021-03-13 16:27:02.887 WARN 21228 --- [reakerFactory-1]
.s.c.o.l.FeignBlockingLoadBalancerClient : Load balancer does not contain an
instance for the service customer-service
Fallback called....
2021-03-13 16:27:03.802 INFO 21228 --- [ main]
o.s.cloud.commons.util.InetUtils : Cannot determine local hostname
..

Spring Cloud - Streams with Apache Kafka

Introduction

In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. In this section, we will look at how services can communicate by asynchronously using message brokers.

Two major benefits of performing asynchronous communication −

  • Producer and Consumer speed can differ − If the consumer of the data is slow or fast, it does not affect the producer processing and vice versa. Both can work at their own individual speeds without affecting each other.

  • Producer does not need to handle requests from various consumers − There maybe multiple consumers who want to read the same set of data from the producer. With a message broker in between, the producer does not need to take care of the load these consumers generate. Plus, any outages at producer level would not block the consumer from reading older producer data, as this data would be available in the message brokers.

Apache Kafka and RabbitMQ are two well-known message brokers used for making asynchronous communication. In this tutorial, we will use Apache Kafka.

Kafka Dependency Setting

Lets use the case of Restaurant that we have been using earlier. So, let us say we have our Customer Service and the Restaurant Service communicating via asynchronous communication. To do that, we will use Apache Kafka. And we will need to use that in both services, i.e., Customer Service and Restaurant Service.

To use Apache Kafka, we will update the POM of both services and add the following dependency.

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>

We also need to have Kafka instances running. There are multiple ways through which it can be done,but we will prefer starting Kafka using Docker container. Here are a few images we can consider using −

Whichever image we use, the important thing here to note is that once the image is up and running,please ensure that the Kafka cluster is accessible at localhost:9092

Now that we have the Kafka cluster running on our image, let's move to the core example.

Binding & Binders

There are three important concepts when it comes to Spring Cloud streams −

  • External Messaging System − This is the component which is managed externally and is responsible to store the events/messages produced by the application that can be read by their subscriber/consumer. Note that this is not managed within the app/Spring. Few examples being Apache Kafka, RabbitMQ

  • Binders − This is the component which provides integration with messaging system, for example, consisting of IP address of messaging system, authentication, etc.

  • Bindings − This component uses the Binders to produce messages to the messaging system or consume the message from a specific topic/queue.

All the above properties are defined in the application properties file.

Example

Let us use the case of Restaurant that we have been using earlier. So, let us suppose whenever a new service is added to the Customer Service, we want to notify the customer info to the nearby Restaurants about him/her.

For this purpose, let us update our Customer Service first to include and use Kafka. Note that we will use Customer Service as a producer of the data. That is, whenever we add the Customer via API, it will also be added to the Kafka.

application-kafka.yml

spring:
   application:
      name: customer-service
   cloud:
      stream:
         source: customerBinding-out-0
         kafka:
            binder:
            brokers: localhost:9092
            replicationFactor: 1
      bindings:
         customerBinding-out-0:
            destination: customer
            producer:
               partitionCount: 3
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8761/eureka

Points to note

  • We have defined a binder with the address of our local Kafka instances.

  • We have also defined the binding customerBinding-out-0 which uses customer topic to output the messages in.

  • We have also mentioned our binding in the stream.source so that we can imperatively use that in our code.

Once this is done, let us now update our controller by adding a new method addCustomer which is responsible to serve the POST request. And then, from the post request, we send the data to the Kafka Broker.

RestaurantCustomerInstancesController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantCustomerInstancesController {
   @Autowired
   private StreamBridge streamBridge;
   static HashMap<Long, Customer> mockCustomerData = new HashMap();
   static{
      mockCustomerData.put(1L, new Customer(1, "Jane", "DC"));
      mockCustomerData.put(2L, new Customer(2, "John", "SFO"));
      mockCustomerData.put(3L, new Customer(3, "Kate", "NY"));
   }
   @RequestMapping("/customer/{id}")
   public Customer getCustomerInfo(@PathVariable("id") Long id) {
      System.out.println("Querying customer for id with: " + id);
      return mockCustomerData.get(id);
   }
   @RequestMapping(path = "/customer/{id}", method = RequestMethod.POST)
   public Customer addCustomer(@PathVariable("id") Long id) {
      // add default name
      Customer defaultCustomer = new Customer(id, "Dwayne", "NY");
      streamBridge.send("customerBinding-out-0", defaultCustomer);
      return defaultCustomer;
   }
}

Points to note

  • We are Autowiring StreamBridge which is what we will use to send the messages.

  • The parameters we use in the send method also specify the binding we want to use to send the data to.

Then, let us compile and start updating Customer Service using the following command −

mvn clean install ; java --app_port=8083 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-kafka.yml

Now let us update our Restaurant Service to include and subscribe to customer topic. Note that we will use Restaurant Service as a consumer of the data. That is, whenever we add the Customer via API, the Restaurant Service would come to know about it via Kafka.

First, let us update the application-kafka yml file.

application-kafka.yml

spring:
   application:
      name: restaurant-service
   cloud:
      function:
         definition: customerBinding
      stream:
         kafka:
            binder:
               brokers: localhost:9092
               replicationFactor: 1
            bindings:
               customerBinding-in-0:
               destination: customer
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8761/eureka

Once this is done, let us now update our controller by adding a new method customerBinding which is responsible to fetch the request and provide a function which will print the request along with its metadata details.

RestaurantController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantController {
   @Autowired
   CustomerService customerService;
   @Autowired
   private StreamBridge streamBridge;
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
      mockRestaurantData.put(4L, new Restaurant(4, "Pizeeria", "NY"));
   }
   @RequestMapping("/restaurant/customer/{id}")
   public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long id) {
      System.out.println("Got request for customer with id: " + id);
      String customerCity = customerService.getCustomerById(id).getCity();
      return mockRestaurantData.entrySet().stream().filter(
entry -> entry.getValue().getCity().equals(customerCity))
.map(entry -> entry.getValue())
.collect(Collectors.toList());
   }
   @RequestMapping("/restaurant/cust/{id}")
   public void getRestaurantForCust(@PathVariable("id") Long id) {
      streamBridge.send("ordersBinding-out-0", id);
   }
   @Bean
   public Consumer<Message<Customer>> customerBinding() {
      return msg -> {
         System.out.println(msg);
      };
   }
}

Points to note

  • We are using customerBinding which is supposed to pass on the function which would be called when a message arrives for this binding.

  • The name that we use for this function/bean also needs to be used in the YAML file while creating the bundling and specifying the topic.

Now, let us execute the above code as always, start the Eureka Server. Note that this is not hard requirement and is present here for the sake of completeness.

Then, let us compile and start updating Restaurant Service using the following command −

mvn clean install; java -Dapp_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-kafka.yml

And we are set, let us now test our code pieces by hitting the API −

curl -X POST http://localhost:8083/customer/1

Here is the output that we will get for this API −

{
   "id": 1,
   "name": "Dwayne",
   "city": "NY"
}

And now, let us check the logs for the Restaurant Service −

GenericMessage [payload=Customer [id=1, name=Dwayne, city=NY],
headers={kafka_offset=1,...

So, effectively, you see that using Kafka Broker, Restaurant Service was notified about the newly added Customer.

Partitions & Consumer Groups

Partitions and Consumer Groups are two important concepts that you should be aware of while using Spring Cloud streams.

Partitions − They are used to partition the data so that we can divide the work between multiple consumers.

Let us see how to partition the data in Spring Cloud. Say, we want to partition the data based on the Customer ID. So, let us update our Customer Service for the same. For that, what we will need to tell

Let us update our Customer Service application yml to specify the key for our data.

application-kafka.yml

spring:
   application:
      name: customer-service
   cloud:
      function:
         definition: ordersBinding
      stream:
         source: customerBinding-out-0
         kafka:
            binder:
               brokers: localhost:9092
               replicationFactor: 1
         bindings:
            customerBinding-out-0:
               destination: customer
               producer:
                  partitionKeyExpression: 'getPayload().getId()'
                  partitionCount: 3
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

For specifying the key, i.e., partitionKeyExpression we provide Spring Expression Language. The expression assumes the type as GenericMessage<Customer> since we are sending the Customer data in the message. Note that GenericMessage is the Spring Framework class used for wrapping the payload and the headers in a single object. So, we get the payload from this message which is of the type Customer and then we call the getId() method on the customer.

Now, let us also update our consumer, i.e., the Restaurant Service to log more info while consuming the request.

Now, let us execute the above code as always, start the Eureka Server. Note that this is not a hard requirement and is present here for the sake of completeness.

Then, let us compile and start updating Customer Service using the following command −

mvn clean install ; java --app_port=8083 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-kafka.yml

Then, let us compile and start updating Restaurant Service using the following command −

mvn clean install; java --app_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar --spring.config.location=classpath:application-kafka.yml

And we are set, let us now test our code pieces. As part of testing, here is what we will do −

  • Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/1

  • Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/1

  • Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/5

  • Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/3

  • Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/1

We do not care much about the output of the API. Rather, we care more about the partition to which the data is sent to. Since we are using customer ID as the key, we expect that the customer with the same ID would end up in the same partition.

And now, let us check the logs for the Restaurant Service −

Consumer: org.apache.kafka.clients.consumer.KafkaConsumer@7d6d8400
Consumer Group: anonymous.9108d02a-b1ee-4a7a-8707-7760581fa323
Partition Id: 1
Customer: Customer [id=1, name=Dwayne, city=NY]
Consumer: org.apache.kafka.clients.consumer.KafkaConsumer@7d6d8400
Consumer Group: anonymous.9108d02a-b1ee-4a7a-8707-7760581fa323
Partition Id: 1
Customer: Customer [id=1, name=Dwayne, city=NY]
Consumer: org.apache.kafka.clients.consumer.KafkaConsumer@7d6d8400
Consumer Group: anonymous.9108d02a-b1ee-4a7a-8707-7760581fa323
Partition Id: 2
Customer: Customer [id=5, name=Dwayne, city=NY]
Consumer: org.apache.kafka.clients.consumer.KafkaConsumer@7d6d8400
Consumer Group: anonymous.9108d02a-b1ee-4a7a-8707-7760581fa323
Partition Id: 0
Customer: Customer [id=3, name=Dwayne, city=NY]
Consumer Group: anonymous.9108d02a-b1ee-4a7a-8707-7760581fa323
Partition Id: 1
Customer: Customer [id=1, name=Dwayne, city=NY]

So, as we see, Customer with Id 1 ended up in the same partition every time, i.e., partition 1.

Consumer Group − A consumer group is the logical grouping of consumers reading the same topic for the same purpose. Data in a topic is partitioned between the consumers in a consumer group so that only one consumer from a given consumer group can read a partition of a topic.

To define a consumer group, all we need to do is define a group in the bindings where we use the Kafka topic name. For example, let us define the consumer group name in our application file for our controller.

application-kafka.yml

spring:
   application:
      name: restaurant-service
   cloud:
      function:
         definition: customerBinding
      stream:
         kafka:
            binder:
               brokers: localhost:9092
               replicationFactor: 1
            bindings:
               customerBinding-in-0:
               destination: customer
               group: restController
server:
   port: ${app_port}
eureka:
   client:
      serviceURL:
         defaultZone: http://localhost:8761/eureka

Let us recompile and start the Restaurant Service. Now, let us generate the event by hitting the POST API on the Customer Service −

Insert a customer with Id 1: curl -X POST http://localhost:8083/customer/1

Now, if we check the logs of our Restaurant Service, we will see the following −

Consumer: org.apache.kafka.clients.consumer.KafkaConsumer@7d6d8400
Consumer Group: restContoller
Partition Id: 1
Customer: Customer [id=1, name=Dwayne, city=NY]

So, as we see from the output, we have a consumer group called rest-contoller created, whose consumers are responsible to read the topics. In the above case, we just had a single instance of the service running, so all the partition of the customer topic was assigned to the same instance. But, if we have multiple partitions, we will have partitions distributed among the workers.

Spring Cloud - Distributed Logging using ELK and Sleuth

Introduction

In a distributed environment or in a monolithic environment, application logs are very critical for debugging whenever something goes wrong. In this section, we will look at how to effectively log and improve traceability so that we can easily look at the logs.

Two major reasons why logging patterns become critical for logging −

  • Inter-service calls − In a microservice architecture, we have async and sync calls between services. It is very critical to link these requests, as there can be more than one level of nesting for a single request.

  • Intra-service calls − A single service gets multiple requests and the logs for them can easily get intermingled. That is why, having some ID associated with the request becomes important to filter all the logs for a request.

Sleuth is a well-known tool used for logging in application and ELK is used for simpler observation across the system.

Dependency Setting

Let us use the case of Restaurant that we have been using in every chapter. So, let us say we have our Customer service and the Restaurant service communicating via API, i.e., synchronous communication. And we want to have Sleuth for tracing the request and the ELK stack for centralized visualization.

To do that, first setup the ELK stack. To do that, first, we will setup the ELK stack. We will be starting the ELK stack using Docker containers. Here are the images that we can consider −

Once ELK setup has been performed, ensure that it is working as expected by hitting the following APIs −

  • Elasticsearch − localhost:9200

  • Kibana − localhost:5601

We will look at logstash configuration file at the end of this section.

Then, let us add the following dependency to our Customer Service and the Restaurant Service −

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

Now that we have the dependency setup and ELK running, let us move to the core example.

Request Tracing inside Service

On a very basic level, following are the metadata that are added by Sleuth −

  • Service name − Service currently processing the request.

  • Trace Id − A metadata ID is added to the logs which is sent across services for processing an input request. This is useful for inter-service communication for grouping all the internal requests which went in processing one input request.

  • Span Id − A metadata ID is added to the logs which is same across all log statements which are logged by a service for processing a request. It is useful for intra-service logs. Note that Span ID = Trace Id for the parent service.

Let us see this in action. For that, let us update our Customer Service code to contain log lines. Here is the controller code that we would use.

RestaurantCustomerInstancesController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantCustomerInstancesController {
   Logger logger =
LoggerFactory.getLogger(RestaurantCustomerInstancesController.class);
   static HashMap<Long, Customer> mockCustomerData = new HashMap();
   static{
      mockCustomerData.put(1L, new Customer(1, "Jane", "DC"));
      mockCustomerData.put(2L, new Customer(2, "John", "SFO"));
      mockCustomerData.put(3L, new Customer(3, "Kate", "NY"));
   }
   @RequestMapping("/customer/{id}")
   public Customer getCustomerInfo(@PathVariable("id") Long id) {
      logger.info("Querying customer with id: " + id);
      Customer customer = mockCustomerData.get(id);
      if(customer != null) {
         logger.info("Found Customer: " + customer);
      }
      return customer;
   }
}

Now let us execute the code, as always, start the Eureka Server. Note that this is not a hard requirement and is present here for the sake of completeness.

Then, let us compile and start updating Customer Service using the following command −

mvn clean install ; java --app_port=8083 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar

And we are set, let us now test our code pieces by hitting the API −

curl -X GET http://localhost:8083/customer/1

Here is the output that we will get for this API −

{
   "id": 1,
   "name": "Jane",
   "city": "DC"
}

And now let us check the logs for Customer Service −

2021-03-23 13:46:59.604 INFO [customerservice,
b63d4d0c733cc675,b63d4d0c733cc675] 11860 --- [nio-8083-exec-7]
.t.RestaurantCustomerInstancesController : Querying customer with id: 1
2021-03-23 13:46:59.605 INFO [customerservice,
b63d4d0c733cc675,b63d4d0c733cc675] 11860 --- [nio-8083-exec-7]
.t.RestaurantCustomerInstancesController : Found Customer: Customer [id=1,
name=Jane, city=DC]
..

So, effectively, as we see, we have the name of the service, trace ID, and the span ID added to our log statements.

Request Tracing across Service

Let us see how we can do logging and tracing across service. So, for example, what we will do is to use the Restaurant Service which internally calls the Customer Service.

For that, let us update our Restaurant Service code to contain log lines. Here is the controller code that we would use.

RestaurantController.java

package com.tutorialspoint.eurekaclient;

import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
class RestaurantController {
   @Autowired
   CustomerService customerService;
   Logger logger = LoggerFactory.getLogger(RestaurantController.class);
   static HashMap<Long, Restaurant> mockRestaurantData = new HashMap();
   static{
      mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC"));
      mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO"));
      mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC"));
      mockRestaurantData.put(4L, new Restaurant(4, "Pizeeria", "NY"));
   }
   @RequestMapping("/restaurant/customer/{id}")
   public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long id) {
      logger.info("Get Customer from Customer Service with customer id: " + id);
      Customer customer = customerService.getCustomerById(id);
      logger.info("Found following customer: " + customer);
      String customerCity = customer.getCity();
      return mockRestaurantData.entrySet().stream().filter(
      entry -> entry.getValue().getCity().equals(customerCity))
      .map(entry -> entry.getValue())
      .collect(Collectors.toList());
   }
}

Let us compile and start updating Restaurant Service using the following command −

mvn clean install; java --app_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar

Ensure that we have the Eureka server and the Customer service running. And we are set, let us now test our code pieces by hitting the API −

curl -X GET http://localhost:8082/restaurant/customer/2

Here is the output that we will get for this API −

[
   {
      "id": 2,
      "name": "Indies",
      "city": "SFO"
   }
]

And now, let us check the logs for Restaurant Service −

2021-03-23 14:44:29.381 INFO [restaurantservice,
6e0c5b2a4fc533f8,6e0c5b2a4fc533f8] 19600 --- [nio-8082-exec-6]
com.tutorialspoint.RestaurantController : Get Customer from Customer Service
with customer id: 2
2021-03-23 14:44:29.400 INFO [restaurantservice,
6e0c5b2a4fc533f8,6e0c5b2a4fc533f8] 19600 --- [nio-8082-exec-6]
com.tutorialspoint.RestaurantController : Found following customer: Customer
[id=2, name=John, city=SFO]

Then, let us check the logs for Customer Service −

2021-03-23 14:44:29.392 INFO [customerservice,
6e0c5b2a4fc533f8,f2806826ac76d816] 11860 --- [io-8083-exec-10]
.t.RestaurantCustomerInstancesController : Querying customer with id: 2
2021-03-23 14:44:29.392 INFO [customerservice,
6e0c5b2a4fc533f8,f2806826ac76d816] 11860 --- [io-8083-exec-10]
.t.RestaurantCustomerInstancesController : Found Customer: Customer [id=2,
name=John, city=SFO]..

So, effectively, as we see, we have the name of the service, trace ID, and the span ID added to our log statements. Plus, we see the trace Id, i.e., 6e0c5b2a4fc533f8 being repeated in Customer Service and the Restaurant Service.

Centralized Logging with ELK

What we have seen till now is a way to improve our logging and tracing capability via Sleuth. However, in microservice architecture, we have multiple services running and multiple instances of each service running. It is not practical to look at the logs of each instance to identify the request flow. And that is where ELK helps us.

Let us use the same case of inter-service communication as we did for Sleuth. Let us update our Restaurant and Customer to add logback appenders for the ELK stack.

Before moving ahead, please ensure that ELK stack has been setup and Kibana is accessible at localhost:5601. Also, configure the Lostash configuration with the following setup −

input {
   tcp {
      port => 8089
      codec => json
   }
}
output {
   elasticsearch {
      index => "restaurant"
      hosts => ["http://localhost:9200"]
   }
}

Once this is done, there are two steps we need to do to use logstash in our Spring app. We will perform the following steps for both our services. First, add a dependency for logback to use appender for Logstash.

<dependency>
<groupId>net.logstash.logback</groupId>
   <artifactId>logstash-logback-encoder</artifactId>
   <version>6.6</version>
</dependency>

And secondly, add an appender for logback so that the logback can use this appender to send the data to Logstash

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="logStash"
class="net.logstash.logback.appender.LogstashTcpSocketAppender">
      <destination>10.24.220.239:8089</destination>
      <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
   </appender>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -
%msg%n</pattern>
      </encoder>
   </appender>
   <root level="INFO">
      <appender-ref ref="logStash" />
      <appender-ref ref="console" />
   </root>
</configuration>

The above appender would log to console as well as send the logs to logstash. Now one this is done, we are all set to test this out.

Now, let us execute the above code as always, start the Eureka Server.

Then, let us compile and start updating Customer Service using the following command −

mvn clean install ; java --app_port=8083 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar

Then, let us compile and start updating Restaurant Service using the following command −

mvn clean install; java --app_port=8082 -jar .\target\eurekaclient-0.0.1-SNAPSHOT.jar

And we are set, let us now test our code pieces by hitting the API −

curl -X GET http://localhost:8082/restaurant/customer/2

Here is the output that we will get for this API −

[
   {
      "id": 2,
      "name": "Indies",
      "city": "SFO"
   }
]

But more importantly, the log statements would also be available on Kibana.

Log Statements

So, as we see, we can filter for a traceId and see all the log statements across services which were logged to fulfill the request.

Advertisements