Anıl Şenocak Anıl Şenocak
Yazılım Mühendisi
Cover image
2026-07-11 0 toplam yorum
Spring Cloud Config Server: Bidirectional Relations

Spring Cloud Config Client: Custom RestTemplate for Port Detection

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:

  • Client-specific configuration refresh
  • Load balancing decisions
  • Health monitoring
  • Targeted bus refresh events

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.

The Solution: Enhanced Custom RestTemplate with Intelligent Port Detection

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.

Critical Component: The spring.factories Bootstrap Registration

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.

Why spring.factories is essential?

The spring.factories file located at src/main/resources/META-INF/spring.factories contains:

org.springframework.cloud.bootstrap.BootstrapConfiguration = org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration

The Bootstrap Phase Challenge

Spring Boot operates in two distinct phases:

  1. Bootstrap Phase: Occurs before the main application context starts.
  2. Application Phase: The normal Spring Boot application startup.

Our custom RestTemplate needs to be available during the bootstrap phase because that’s when:

  • Configuration properties are fetched from the config server
  • The ConfigServicePropertySourceLocator is initialized
  • Network communication with the config server occurs

Without spring.factories: The Problem

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  
}

With spring.factories: The Solution

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

Bootstrap vs Application Context

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

File Structure Requirements

The file must be placed in the exact location:

src/main/resources/META-INF/spring.factories

Critical Points:

  • Path must be exact: META-INF/spring.factories (not `meta-inf` or `Meta-Inf`)
  • Format matters: `key = value` format
  • Classpath visibility: Must be in src/main/resources to be included in the JAR

Multiple Bootstrap Configurations

If you need multiple bootstrap configurations, separate them with commas:

org.springframework.cloud.bootstrap.BootstrapConfiguration = \\  
  org.github.senocak.sccsjc.CustomConfigServiceBootstrapConfiguration,\\  
  com.example.AnotherBootstrapConfiguration

Common Pitfalls and Solutions

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

Verification: How to Confirm It’s Working

You can verify the bootstrap configuration is working by:

  1. Log Output: Look for your custom RestTemplate logs during startup
  2. Server Logs: Check if the config server receives the `X-Client-Port` header
  3. Debug Mode: Enable debug logging to see bootstrap context creation
\# 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

Setting Up the Custom Configuration

Enhanced Configuration Bootstrap

@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  
    }  
}

Robust Dynamic Port Discovery

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}")  
    }  
}

Intelligent Port Detection and RestTemplate Implementation

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  
}

Bootstrap Environment Configuration

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) }  
}

Production Considerations

Error Handling

The implementation includes robust error handling:

  • IOException handling in port discovery
  • Null safety with Kotlin’s safe operators
  • Proper resource cleanup with use blocks

Performance Optimizations

  • Lazy port evaluation only when needed
  • Efficient environment property loading
  • Minimal overhead interceptor implementation

Security Implications

  • Port information is sent in headers (consider HTTPS in production)
  • Environment property access is controlled
  • No sensitive information exposure

Conclusion

This enhanced CustomConfigServiceBootstrapConfiguration represents a production-ready solution for client port detection in Spring Cloud Config environments. The implementation combines:

  • Robustness: Comprehensive error handling and resource management.
  • Flexibility: Support for both fixed and dynamic port allocation
  • Performance: Optimized for minimal overhead
  • Maintainability: Well-documented with clear separation of concerns

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:

  • Perform targeted refreshes
  • Health check clients based on their ports
  • Maintain a detailed client registry
  • Enable advanced load balancing and health checks
  • Integrate seamlessly with Spring Cloud Bus for real-time updates

Full code example can be found in github

Yorumlar
Henüz yorum yok. İlk yorumu sen yaz.
Yorum Bırak
Adınız (isteğe bağlı)
Yorumunuzu yazın...
0/500