Config Server & Service Discovery

Centralized configuration with Spring Cloud Config Server, and service discovery with Eureka.

Spring Cloud Config Server

Without centralized configuration, each service's application.yml holds its own copy of shared settings — a database URL, a shared feature flag, a third-party API key — and changing one means editing and redeploying every service that has a copy. Config Server centralizes this: services fetch their configuration from one place at startup, typically backed by a Git repository so configuration changes are version-controlled just like code.

Plaintext
┌──────────────────┐        ┌───────────────────────┐
│  Git repo            │ <----- │   Config Server           │
│  (config files)      │        │  (spring-cloud-config-server) │
└──────────────────┘        └───────────┬───────────┘
                                          │  GET /orders-service/prod
                                          v
                              ┌───────────────────────┐
                              │    Orders Service          │  <- fetches its config at startup
                              └───────────────────────┘

Enabling a Config Server is a single annotation on a dedicated Spring Boot application:

Java
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
YAML
# Config Server's own application.yml
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/example-org/config-repo

A client service points at it instead of (or in addition to) its own local application.yml:

YAML
# orders-service's bootstrap configuration
spring:
  application:
    name: orders-service
  config:
    import: "configserver:http://localhost:8888"

At startup, orders-service requests configuration matching its own application name and active profile from the Config Server, which resolves the matching file(s) from the Git repository and returns them — the same mechanism as application-{profile}.yml, just centralized and shared instead of duplicated per service.

Service discovery

Once there are multiple instances of multiple services, each one potentially starting, stopping, and being rescheduled to a different host by an orchestrator, hardcoding another service's address (http://10.0.4.12:8080) stops working — that address is only valid until the instance is rescheduled. Service discovery solves this: every instance registers itself with a shared registry on startup, and any service that needs to call another asks the registry for a currently-healthy instance instead of using a fixed address.

Plaintext
Orders Service instance starts up
      │
      v
 registers itself with the registry:
 "I am orders-service, I'm at 10.0.4.12:8080, and I'm healthy"
      │
      v
┌───────────────────┐
│   Eureka Server       │  <- keeps a live registry of every registered instance
└───────────────────┘
      ^
      │  "give me a healthy instance of orders-service"
      │
 Payments Service (needs to call Orders)

Eureka (Netflix's discovery server, integrated via Spring Cloud Netflix) is the most common choice in the Spring ecosystem; Consul is a popular alternative with the same underlying idea. A minimal Eureka server:

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

A service registers itself as a Eureka client with one annotation and a pointer to the registry:

Java
@SpringBootApplication
@EnableDiscoveryClient
public class OrdersServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrdersServiceApplication.class, args);
    }
}
YAML
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

Once registered, another service can call it by logical name instead of a hardcoded address, using a discovery-aware RestClient/WebClient or RestTemplate, or (more commonly in current Spring Cloud) through Spring Cloud Gateway's own discovery-aware routing (covered next):

Java
@Service
public class PaymentsClient {
    private final RestClient restClient;

    public PaymentsClient(RestClient.Builder builder) {
        // "orders-service" is resolved to a live instance's address via the registry, not hardcoded
        this.restClient = builder.baseUrl("http://orders-service").build();
    }
}

Common mistakes

  • Hardcoding a specific instance's IP/port for service-to-service calls instead of going through discovery — works until that instance is rescheduled, then breaks silently.
  • Storing sensitive config (database passwords, API keys) directly in the Config Server's backing Git repository in plain text instead of using its encryption support or an external secrets manager.
  • Not planning for the Config Server or discovery registry itself being a single point of failure — both are typically run with multiple replicas in a real production setup, exactly like any other critical service.