How to Add Custom Headers to a Specific CoApi Client Using WebClientBuilderCustomizer
Implement a Spring bean that implements WebClientBuilderCustomizer, override the customize method to call defaultHeader() or defaultHeaders() on the WebClient.Builder, and filter by CoApiDefinition to target a specific client.
CoApi generates HTTP clients based on Spring WebClient, and you can intercept the client creation process to inject custom headers that apply to every request. By providing a custom WebClientBuilderCustomizer implementation, you modify the underlying WebClient.Builder before the final client bean is instantiated, ensuring your headers are present on all outbound calls from that specific CoApi client.
Understanding the WebClientBuilderCustomizer Extension Point
CoApi delegates WebClient construction to WebClientFactoryBean, which accepts one or more WebClientBuilderCustomizer implementations to configure the builder. According to the ahoo-wang/coapi source code, this interface extends the base HttpClientBuilderCustomizer and provides access to both the CoApiDefinition (metadata about the client being built) and the WebClient.Builder instance.
In spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientBuilderCustomizer.kt, the interface defines a single method:
fun customize(coApiDefinition: CoApiDefinition, builder: WebClient.Builder)
The CoApiDefinition parameter contains identifying information such as the client name, allowing you to conditionally apply headers only to specific clients while leaving others unaffected.
Creating a Targeted Customizer for a Single Client
To add custom headers to only one specific CoApi client, implement the customizer interface and filter by the definition name.
1. Implement the Interface as a Spring Bean
Create a @Service or @Component class that implements WebClientBuilderCustomizer. Spring automatically detects and registers this bean during component scanning.
2. Filter by Client Name
Inside the customize method, check coApiDefinition.name to determine which client is being constructed. Return early if the definition does not match your target client.
3. Apply Default Headers
Use builder.defaultHeader() for single headers or builder.defaultHeaders() for bulk operations. These methods attach headers to every request made by the resulting WebClient.
package me.ahoo.coapi.example.consumer
import me.ahoo.coapi.spring.CoApiDefinition
import me.ahoo.coapi.spring.client.reactive.WebClientBuilderCustomizer
import org.springframework.http.HttpHeaders
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
@Service
class GithubClientHeaderCustomizer : WebClientBuilderCustomizer {
override fun customize(coApiDefinition: CoApiDefinition, builder: WebClient.Builder) {
// Target only the "github" client
if (coApiDefinition.name != "github") {
return
}
// Add authentication header
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer my-static-token")
// Add custom metadata headers
builder.defaultHeader("X-Client-Id", "example-consumer")
// Or configure multiple headers at once
builder.defaultHeaders { httpHeaders ->
httpHeaders.addAll(
mapOf(
"X-Request-Source" to "coapi-consumer",
"X-Trace-Id" to "generated-trace-id"
)
)
}
}
}
Applying Headers Globally to All CoApi Clients
If you want the same headers added to every CoApi client in your application, omit the name check. This approach is useful for correlation IDs, global API keys, or user-agent strings that should be universal across all HTTP clients.
@Service
class GlobalHeaderCustomizer : WebClientBuilderCustomizer {
override fun customize(coApiDefinition: CoApiDefinition, builder: WebClient.Builder) {
// Applies to every CoApiDefinition
builder.defaultHeader("X-Global-Header", "globalValue")
builder.defaultHeader("X-Application-Name", "my-service")
}
}
How the Initialization Flow Works
Understanding the bean creation sequence helps debug why headers might not appear as expected. The process involves four key components from the ahoo-wang/coapi repository:
-
CoApiRegistrar(spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiRegistrar.kt): Scans the application context for allCoApiDefinitionbeans and triggers the creation of corresponding client beans. -
WebClientFactoryBean(spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientFactoryBean.kt): For each definition, this factory bean creates theWebClientinstance. It retrieves allWebClientBuilderCustomizerbeans from the Spring context. -
AbstractWebClientFactoryBean(spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/AbstractWebClientFactoryBean.kt): The base implementation that orchestrates the builder configuration. It iterates through the collected customizers and invokescustomize(coApiDefinition, builder)for each one. -
WebClientBuilderCustomizer: Your implementation modifies the builder beforeAbstractWebClientFactoryBeancallsbuilder.build()to create the finalWebClientbean.
This architecture ensures that headers are injected at the lowest level of the HTTP stack, guaranteeing they appear on every request regardless of which CoApi client method is invoked.
Summary
- Implement
WebClientBuilderCustomizeras a Spring-managed bean to intercept WebClient construction. - Use
defaultHeader()ordefaultHeaders()on theWebClient.Builderto attach static headers to all requests. - Filter by
CoApiDefinition.nameto restrict header injection to a specific CoApi client while leaving others unchanged. - Rely on
CoApiRegistrarandWebClientFactoryBeanto automatically detect and apply your customizer during the client initialization phase.
Frequently Asked Questions
Can I add dynamic headers that change per request?
The defaultHeader and defaultHeaders methods apply static values to every request. For dynamic headers that vary per request (such as timestamps or request-scoped tokens), use WebClient's ExchangeFilterFunction instead, which can modify the ClientRequest on each execution. You can add filters via the same WebClient.Builder inside your customizer using builder.filter().
How do I target multiple specific clients with one customizer?
Inside the customize method, maintain a set of target client names and check if coApiDefinition.name is contained within that set. Alternatively, use pattern matching or check other properties of the CoApiDefinition object to determine if the customizer should apply headers to the current client being built.
What is the difference between defaultHeader and defaultHeaders?
defaultHeader(key, value) adds a single header key-value pair to every request. defaultHeaders(Consumer<HttpHeaders>) accepts a lambda that receives the HttpHeaders object, allowing you to add multiple headers, manipulate existing ones, or conditionally set headers based on logic within the consumer block. Both methods store headers in the underlying WebClient builder.
Where does the customizer fit in the Spring bean lifecycle?
The customizer is invoked during the instantiation phase of the CoApi client bean, specifically when WebClientFactoryBean creates the WebClient instance. This occurs after Spring's dependency injection container has initialized the customizer bean itself but before the CoApi client proxy is fully constructed and injected into your service classes. This timing ensures the headers are baked into the WebClient before any HTTP calls are made.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →