Config Clients and Profiles

Connect a Service

Add spring-cloud-starter-config to order-service and import the server in its own small application.yml:

spring:
  application:
    name: order-service        # decides which files are fetched
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}
  config:
    import: "optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888}"
  cloud:
    config:
      fail-fast: false          # true in prod: refuse to start without config

Example

SPRING_PROFILES_ACTIVE=prod java -jar order-service.jar

Example output

INFO  o.s.c.c.c.ConfigServerConfigDataLoader : Fetching config from server at : http://localhost:8888
INFO  o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=order-service, profiles=[prod], label=null, version=4c1e9a7d, state=null
INFO  c.s.order.OrderServiceApplication       : The following 1 profile is active: "prod"

Note: optional: means "start anyway if the server is down" (using local values). Without it the app fails to start. The old bootstrap.yml way is no longer needed since Spring Cloud 2020.

Which Value Wins?

More specific sources override less specific ones. For order-service with profile prod, from weakest to strongest: local application.yml → repo application.yml → order-service.yml → order-service-prod.yml. This plain-Java program merges layers the same way and shows where each final value came from:

Example

import java.util.*;

public class Main {
    public static void main(String[] args) {
        // weakest first
        List<Layer> layers = List.of(
            new Layer("local application.yml", Map.of(
                "server.port", "8082", "shopease.orders.max-items-per-order", "20")),
            new Layer("repo application.yml", Map.of(
                "management.endpoints.web.exposure.include", "health,info,refresh")),
            new Layer("repo order-service.yml", Map.of(
                "shopease.orders.max-items-per-order", "10", "shopease.orders.cod-enabled", "true")),
            new Layer("repo order-service-prod.yml", Map.of(
                "shopease.orders.max-items-per-order", "5"))
        );

        Map<String, String> value = new TreeMap<>();
        Map<String, String> source = new TreeMap<>();
        for (Layer layer : layers) {
            layer.props().forEach((k, v) -> {
                value.put(k, v);           // later layer overrides
                source.put(k, layer.name());
            });
        }
        value.forEach((k, v) ->
            System.out.printf("%-42s = %-20s <- %s%n", k, v, source.get(k)));
    }
}

record Layer(String name, Map<String, String> props) {}

Output

management.endpoints.web.exposure.include  = health,info,refresh  <- repo application.yml
server.port                                = 8082                 <- local application.yml
shopease.orders.cod-enabled                = true                 <- repo order-service.yml
shopease.orders.max-items-per-order        = 5                    <- repo order-service-prod.yml

Use the Values in Code

Prefer a typed @ConfigurationProperties record over many @Value fields (the Spring Boot material covers this in depth):

@ConfigurationProperties(prefix = "shopease.orders")
public record OrderProperties(int maxItemsPerOrder, boolean codEnabled) {}

@SpringBootApplication
@ConfigurationPropertiesScan
public class OrderServiceApplication { /* main */ }

@Service
@RequiredArgsConstructor
class OrderRules {
    private final OrderProperties props;

    void check(PlaceOrderRequest req) {
        if (req.items().size() > props.maxItemsPerOrder()) {
            throw new TooManyItemsException(props.maxItemsPerOrder());
        }
        if (req.paymentMode() == PaymentMode.COD && !props.codEnabled()) {
            throw new CodNotAvailableException();
        }
    }
}