Introduction — Leaving Printed Addresses Behind#
Hard‑coding IPs in microservices is like using a paper map in the GPS era. For years Netflix Eureka was the natural choice in Spring Cloud projects: easy integration, reliable, familiar. Over time we needed more: efficiency, richer features and an active roadmap.
This guide summarizes a real migration I performed from Eureka to HashiCorp Consul: why we decided to switch, the practical steps, issues faced, and measurable benefits in production—plus clear examples and actionable recommendations.

What Service Discovery Really Is#
Service Discovery is the GPS for microservices: a service locates dynamically the IP and port of another service. It avoids static configuration, enables automatic scaling and improves resilience.
Common patterns:
- Client‑side discovery: the client queries the registry (Eureka/Consul) and selects an instance.
- Server‑side discovery: a proxy or router (Kubernetes Service, load balancer) resolves on your behalf.
If you still trust fixed IPs, your code needs an urgent conversation… and a coffee.

Why Eureka Was the Standard (And Why We Liked It)#
Eureka gained traction because integration with Spring Cloud was simple: a few dependencies, a couple annotations and apps self‑registered. Historical advantages:
- Fast setup thanks to auto‑configuration.
- AP philosophy: high availability tolerating stale registrations.
- Natural integration with the Spring Cloud stack.
Yet limitations began to outweigh convenience.
Limitations That Pushed the Migration#
1) Stagnant development
Eureka moved into maintenance mode. Building on a project without a roadmap adds technical debt.
2) Resource consumption
Eureka Server is a full JVM: we measured ~300–500 MB RAM per node. In larger clusters that adds up.
3) Limited functionality
Eureka does discovery, but not a KV store, advanced health checks or integrated mesh.
If your goal is a modern platform, relying solely on Eureka today is like owning a Swiss Army knife without the blade.

Why HashiCorp Consul (More Than Discovery)#
Consul is a distributed networking and configuration platform with clear advantages:
- Robust Service Discovery.
- Advanced health checks (HTTP, TCP, scripts, gRPC).
- Key‑Value store for distributed configuration.
- Service Mesh with Consul Connect (mTLS and routing).
- Native multi‑datacenter support.
- Lightweight implementation (written in Go).
In practice: fewer components, more features, active roadmap.
Quick Comparison#
- Language: Eureka = Java, Consul = Go.
- RAM per node: Eureka ≈ 300–500 MB, Consul ≈ 40–80 MB.
- Development: Eureka maintenance; Consul active.
- KV Store: Eureka ❌, Consul ✅.
- Service Mesh: Eureka ❌, Consul ✅ (Consul Connect).
- Health Checks: Eureka basic; Consul advanced.
- UI: Eureka simple; Consul modern & usable.

Migration Plan: Practical Focus#
Most effort is configuration; application code barely changes. Four steps overview:
- Update dependencies (
pom.xml) - Adjust configuration (
bootstrap.yml/application-*.yml) - Verify / adapt code (minimal changes)
- Orchestrate with Docker Compose / CI / Kubernetes
Step 1 — Dependencies (pom.xml)#
<!-- REMOVE -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- ADD -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
Note:
spring-cloud-starter-bootstraplets Spring contact Consul during the bootstrap phase. Keep it.
Step 2 — Configuration: bootstrap.yml and application-docker.yml#
Keep bootstrap.yml minimal: only what is essential to reach Consul.
bootstrap.yml
spring:
application:
name: ms-customer
cloud:
consul:
host: consul
port: 8500
In the Docker profile (application-docker.yml) define registration & health checks:
application-docker.yml
spring:
cloud:
consul:
discovery:
service-name: ${spring.application.name}
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
health-check-path: /actuator/health/liveness
health-check-interval: 10s
management:
endpoint:
health:
probes:
enabled: true
Key points:
prefer-ip-address: trueis critical in Docker to avoidlocalhost.- Use Actuator for real health checks.
- Keep
bootstrap.ymllean; move runtime properties toapplication-*.ymlto avoid bootstrap conflicts.
Step 3 — Java Code: Nearly Drama‑Free#
Spring Cloud abstractions (@LoadBalanced RestTemplate, FeignClient, etc.) usually require no changes. Typical example:
@SpringBootApplication
public class CustomerApplication {
// unchanged code
}
@FeignClient(name = "ms-activity")
public interface ActivityClient { /* ... */ }
Note: @EnableDiscoveryClient is optional in Spring Boot 3.x; Spring Cloud auto‑configures discovery.
Step 4 — Orchestration: Minimal Docker Compose#
Minimal example for local tests:
services:
consul:
image: hashicorp/consul:latest
ports: ["8500:8500"]
command: agent -server -ui -bootstrap-expect=1 -client=0.0.0.0
ms-customer:
build: .
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- consul
Tips:
depends_onhelps in Compose; in Kubernetes rely on readiness probes.- In production configure probes, network policies and security.

Common Issues & Practical Fixes#
1) Service does not appear in Consul
Cause: bloated or conflicting bootstrap.yml.
Fix: simplify bootstrap.yml, move runtime properties to application-*.yml.
2) Registers with localhost or wrong IP
Cause: registration using localhost under Docker.
Fix: prefer-ip-address: true and check Docker network config.
3) Health check fails and service deregisters
Cause: wrong path or container networking issue.
Fix: validate the endpoint from the Consul container (curl) and correct path/port.
Debugging: enter the Consul container, inspect catalog & health state. Often it’s a mistyped path or mismatched port.
Results: Metrics & Benefits#
Memory consumption
- Eureka: ~400 MB per node
- Consul: ~60 MB per node
- Approx. savings: ~85% in discovery service memory
Failure detection time
- Eureka: ~90 s
- Consul: ~20 s
- Detection ~4.5× faster → quicker recovery & less user impact
Qualitative benefits:
- Friendlier UI for debugging.
- Integrated KV store → fewer stack pieces.
- Path toward service mesh with Consul Connect (mTLS & policies).
- Lower operational cost (less RAM, lighter processes).

Roadmap & Next Steps#
- Security & secrets management
- Implement Consul ACLs.
- Integrate HashiCorp Vault for secrets.
- Service Mesh
- Enable Consul Connect for automatic mTLS and policy control.
- Runtime optimization
- Evaluate native image (GraalVM) to reduce memory & startup time.
Vault + Consul Connect opens many possibilities but requires security design & reviews.
Conclusion — Is Migration Worth It?#
For us it was a strategic investment: modernized the platform, reduced costs and paved the way for service mesh and centralized configuration/secrets. Most effort was configuration; application code stayed almost untouched. Net result: an achievable migration for most teams following good practices.
Want me to prepare an example repository and a step‑by‑step checklist? Tell me if you prefer Docker Compose for local tests or Kubernetes manifests for staging/production. Let’s talk microservices—no paper maps needed!
