Spring BootEureka Client
Spring Boot

Eureka Client

A Eureka Client is any microservice that registers with the Eureka Server and uses it to discover other services. On startup it publishes its host, port, and health URL to the registry. It periodically fetches the registry from the server into a local cache and uses that cache — together with Spring Cloud LoadBalancer — to resolve logical service names to real network addresses.

Setting Up a Eureka Client

Add the eureka-client starter to any microservice. Spring Boot auto-configuration registers the service with Eureka on startup and deregisters it on graceful shutdown. The only mandatory configuration is the application name (used as the registry key) and the Eureka Server URL.
XML
<!-- pom.xml: -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</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-actuator</artifactId>
    </dependency>
</dependencies>

Eureka Client Application Class and Configuration

@EnableDiscoveryClient activates registration and discovery. In Spring Cloud 3.x this annotation is optional — the auto-configuration activates automatically when the Eureka client starter is on the classpath — but it is good practice to include it for explicitness.
yaml
// ── Main application class: ──────────────────────────────────────────
@SpringBootApplication
@EnableDiscoveryClient   // optional in Spring Cloud 3.x but explicit
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

// ── application.yml: ──────────────────────────────────────────────────
// server:
//   port: 8081
//
// spring:
//   application:
//     name: user-service          # ← registry key; used in lb:// URIs
//
// eureka:
//   client:
//     service-url:
//       defaultZone: http://localhost:8761/eureka/
//     register-with-eureka: true  # default: true
//     fetch-registry: true        # default: true
//     registry-fetch-interval-seconds: 30   # refresh local cache every 30s
//
//   instance:
//     prefer-ip-address: true     # register IP instead of hostname
//     lease-renewal-interval-in-seconds: 30   # heartbeat frequency
//     lease-expiration-duration-in-seconds: 90 # evicted after 3 missed
//     instance-id: ${spring.application.name}:${server.port}
//     # instance-id makes each instance identifiable on the dashboard

// ── What happens on startup: ──────────────────────────────────────────
// 1. Application starts → EurekaAutoServiceRegistration fires
// 2. POST /eureka/apps/USER-SERVICE  (registers this instance)
// 3. Eureka Server stores: { host, port, healthUrl, status: UP }
// 4. Every 30s: PUT /eureka/apps/USER-SERVICE/{id}  (heartbeat)
// 5. On shutdown: DELETE /eureka/apps/USER-SERVICE/{id}

Calling Other Services via Eureka

Once registered, a service can call peers using their logical name. Spring Cloud LoadBalancer (included with the Eureka client starter) intercepts calls made with RestTemplate @LoadBalanced or WebClient and resolves the service name to a real instance from the local registry cache.
Java
// ── Option 1: @LoadBalanced RestTemplate ─────────────────────────────
@Configuration
public class AppConfig {

    @Bean
    @LoadBalanced   // instructs Spring Cloud LoadBalancer to intercept
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@Service
@RequiredArgsConstructor
public class OrderService {

    private final RestTemplate restTemplate;

    public UserResponse getUser(Long id) {
        // "user-service" is the spring.application.name of the target.
        // LoadBalancer resolves it to a real host:port via Eureka.
        return restTemplate.getForObject(
            "http://user-service/api/users/" + id,
            UserResponse.class
        );
    }
}

// ── Option 2: @LoadBalanced WebClient (non-blocking): ─────────────────
@Configuration
public class WebClientConfig {

    @Bean
    @LoadBalanced
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

@Service
@RequiredArgsConstructor
public class OrderService {

    private final WebClient.Builder webClientBuilder;

    public Mono<UserResponse> getUser(Long id) {
        return webClientBuilder.build()
            .get()
            .uri("http://user-service/api/users/" + id)
            .retrieve()
            .bodyToMono(UserResponse.class);
    }
}

// ── Option 3: DiscoveryClient — manual instance resolution: ───────────
@Service
@RequiredArgsConstructor
public class DiscoveryService {

    private final DiscoveryClient discoveryClient;

    public void printInstances() {
        List<ServiceInstance> instances =
            discoveryClient.getInstances("user-service");

        instances.forEach(i ->
            System.out.printf("Host: %s  Port: %d  URI: %s%n",
                i.getHost(), i.getPort(), i.getUri())
        );
    }
}

Load Balancing Strategies

Spring Cloud LoadBalancer ships with two built-in strategies: RoundRobinLoadBalancer (default — distributes requests evenly) and RandomLoadBalancer. Custom strategies can be implemented by extending ReactorServiceInstanceLoadBalancer. The strategy is configured per target service.
Java
// ── Default: RoundRobinLoadBalancer ──────────────────────────────────
// Requests to "user-service" are sent to instances in rotation:
//   Request 1172.18.0.5:8081
//   Request 2172.18.0.6:8081
//   Request 3172.18.0.5:8081  (back to first)

// ── Override to RandomLoadBalancer for a specific service: ────────────
@Configuration
@LoadBalancerClient(
    name = "user-service",           // applies only to user-service calls
    configuration = RandomLBConfig.class
)
public class AppConfig { }

public class RandomLBConfig {

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment env,
            LoadBalancerClientFactory factory) {
        String name = env.getProperty(
            LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
            factory.getLazyProvider(name,
                ServiceInstanceListSupplier.class),
            name
        );
    }
}

// ── Override globally (all services): ────────────────────────────────
@Configuration
@LoadBalancerClients(defaultConfiguration = RandomLBConfig.class)
public class GlobalLoadBalancerConfig { }

// ── Custom load balancer (e.g. prefer same-zone instances): ───────────
public class ZoneAwareLoadBalancer implements
        ReactorServiceInstanceLoadBalancer {

    private final ObjectProvider<ServiceInstanceListSupplier> supplier;
    private final String serviceId;
    private final String currentZone;

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        return supplier.getIfAvailable(NoopServiceInstanceListSupplier::new)
            .get(request)
            .next()
            .map(instances -> {
                List<ServiceInstance> sameZone = instances.stream()
                    .filter(i -> currentZone.equals(
                        i.getMetadata().get("zone")))
                    .toList();
                List<ServiceInstance> pool =
                    sameZone.isEmpty() ? instances : sameZone;
                return new DefaultResponse(
                    pool.get(ThreadLocalRandom.current()
                        .nextInt(pool.size())));
            });
    }
}

Instance Metadata and Zone Awareness

Each Eureka instance can publish arbitrary key-value metadata alongside its registration. Metadata is visible on the Eureka dashboard and accessible to other services via ServiceInstance.getMetadata(). Common uses include zone/region tagging, version labels for canary deployments, and feature flags.
Java
// ── Publishing metadata from a client instance: ──────────────────────

// application.yml:
// eureka:
//   instance:
//     metadata-map:
//       zone: us-east-1a          # availability zone
//       version: 2.1.0            # for canary / blue-green routing
//       team: payments            # ownership tag
//       weight: "80"              # custom traffic weight for canary

// ── Reading metadata in another service: ──────────────────────────────
@Service
@RequiredArgsConstructor
public class MetadataAwareService {

    private final DiscoveryClient discoveryClient;

    public Optional<ServiceInstance> findCanaryInstance(
            String serviceName) {
        return discoveryClient.getInstances(serviceName).stream()
            .filter(i -> "canary".equals(
                i.getMetadata().get("version")))
            .findFirst();
    }

    public void printAllMetadata(String serviceName) {
        discoveryClient.getInstances(serviceName).forEach(instance -> {
            System.out.println("Instance: " + instance.getInstanceId());
            instance.getMetadata()
                .forEach((k, v) -> System.out.println("  " + k + "=" + v));
        });
    }
}

// ── Programmatic metadata registration: ──────────────────────────────
@Configuration
public class EurekaInstanceConfig {

    @Bean
    public EurekaInstanceConfigBean eurekaInstanceConfig(
            InetUtils inetUtils) {
        EurekaInstanceConfigBean config =
            new EurekaInstanceConfigBean(inetUtils);
        config.getMetadataMap().put("zone", "us-east-1a");
        config.getMetadataMap().put("version",
            getClass().getPackage().getImplementationVersion());
        return config;
    }
}