Anıl Şenocak

In a distributed system with Spring Cloud Config, the config server needs to track which clients are connected and their network details. This becomes crucial when implementing features like:
The challenge is that the config server only sees HTTP requests coming in, but doesn’t inherently know which port the client is running on. This information is vital for bidirectional communication.
Our solution involves creating a sophisticated custom RestTemplate that automatically injects the client’s port information into every request header, with enhanced error handling and environment setup.
Before diving into the implementation details, it’s crucial to understand why we will need the spring.factories file. This file is the bridge that makes our custom configuration work during Spring’s bootstrap phase.
The spring.factories file located at src/main/resources/META-INF/spring.factories contains:
org.springframework.cloud.bootstrap.BootstrapConfiguration = org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration
Spring Boot operates in two distinct phases:
Our custom RestTemplate needs to be available during the bootstrap phase because that’s when:
If we simply annotated our class with @Configuration or @Component, it would be registered during the application phase, but the bootstrap phase would have already completed. This means:
// This WON'T work for bootstrap configuration
@Configuration // ❌ Too late - bootstrap already happened
class CustomConfigServiceBootstrapConfiguration {
// This bean won't be available during config fetching
}
The spring.factories mechanism allows Spring to discover and register our configuration class specifically during the bootstrap phase:
\# This ensures our configuration is available BEFORE config loading
org.springframework.cloud.bootstrap.BootstrapConfiguration = org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration
Here’s the execution timeline:
1\. Spring Boot Starts
2\. Bootstrap Context Created
├── spring.factories discovered ✅
├── CustomConfigServiceBootstrapConfiguration loaded ✅
├── Custom RestTemplate with X-Client-Port header created ✅
└── Configuration fetched from server with port information ✅
3\. Application Context Created
├── Regular @Configuration classes loaded
└── Application starts with correct configuration
The file must be placed in the exact location:
src/main/resources/META-INF/spring.factories
Critical Points:
If you need multiple bootstrap configurations, separate them with commas:
org.springframework.cloud.bootstrap.BootstrapConfiguration = \\
org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration,\\
com.example.AnotherBootstrapConfiguration
Pitfall 1: Wrong File Location
❌ src/main/resources/spring.factories # Missing META-INF
❌ src/main/resources/META-INF/Spring.factories # Wrong case
✅ src/main/resources/META-INF/spring.factories # Correct
Pitfall 2: Incorrect Key
❌ org.springframework.boot.autoconfigure.EnableAutoConfiguration
❌ org.springframework.context.ApplicationContextInitializer
✅ org.springframework.cloud.bootstrap.BootstrapConfiguration
Pitfall 3: Class Not Found
\# Ensure the class exists and package name is correct
org.springframework.cloud.bootstrap.BootstrapConfiguration = org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration
\# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
\# Must match actual package and class name exactly
You can verify the bootstrap configuration is working by:
\# application.yml - for debugging
logging:
level:
org.springframework.cloud.bootstrap: DEBUG
org.github.senocak.sccsjc: DEBUG
When working correctly, you’ll see:
Adding header X-Client-Port: 8081 to request http://localhost:8080/sccsj/client1/dev/master
@Configuration
class CustomConfigServiceBootstrapConfiguration {
@Bean
@Primary
fun configServicePropertySourceLocator(
clientProperties: ConfigClientProperties
): ConfigServicePropertySourceLocator {
val locator = ConfigServicePropertySourceLocator(clientProperties)
val env = StandardEnvironment()
env.setActiveProfiles(clientProperties.profile)
// Setup environment from bootstrap.yml
setEnvironment(environment = env)
// Create and inject our custom RestTemplate
val restTemplate = customRestTemplate(environment = env)
locator.setRestTemplate(restTemplate)
return locator
}
}
The port discovery mechanism now includes proper resource management:
private fun findAvailablePort(): Int {
try {
ServerSocket(0).use { socket ->
return socket.localPort
}
} catch (e: IOException) {
throw RuntimeException("Unable to find an available port: ${e.localizedMessage}")
}
}
The enhanced custom RestTemplate now includes sophisticated port handling:
private fun customRestTemplate(environment: Environment): RestTemplate {
val restTemplate = RestTemplate()
// Enhanced port detection with intelligent handling
val port: String = environment.getProperty("server.port")?.takeIf { it != "0" }
?: findAvailablePort().toString().also { System.setProperty("server.port", it) }
// Add interceptor with enhanced logging
restTemplate.interceptors.add { request, body, execution ->
request.headers.add("X-Client-Port", port)
println("Adding header X-Client-Port: $port to request ${request.uri}")
execution.execute(request, body)
}
return restTemplate
}
A new utility method handles proper environment setup:
fun setEnvironment(environment: Environment) {
YamlPropertySourceLoader()
.load("applicationConfig", ClassPathResource("bootstrap.yml"))
.firstOrNull()
?.let { (environment as ConfigurableEnvironment).propertySources.addLast(it) }
}
The implementation includes robust error handling:
This enhanced CustomConfigServiceBootstrapConfiguration represents a production-ready solution for client port detection in Spring Cloud Config environments. The implementation combines:
The solution elegantly solves the distributed configuration challenge while providing a foundation for sophisticated client management strategies in modern microservice architectures.
Since the config server has the port information in every request header, it can now:
Full code example can be found in github