Spring 7's Declarative HttpClient: The FeignClient Alternative
Spring 7 introduces a native declarative HttpClient built on RestClient and WebClient. It uses familiar annotations like @GetExchange and @PostExchange, similar to @GetMapping in controllers. This lets you define outgoing HTTP requests through interfaces without manual implementation.
Here's what the key interface looks like:
public interface UserClient {
@GetExchange("/api/users/{id}")
UserResponse getUser(
@PathVariable("id") Long id,
@RequestParam(name = "includeDetails", defaultValue = "false") boolean includeDetails,
@RequestHeader("Authorization") String authToken,
@RequestHeader("X-Request-Id") String requestId
);
@PostExchange("/api/users")
UserResponse createUser(
@RequestBody @Valid CreateUserRequest request,
@RequestHeader("Authorization") String authToken
);
@PatchExchange("/api/users/{id}")
void updateUserEmail(
@PathVariable("id") Long id,
@RequestParam("email") String email,
@RequestHeader("Authorization") String authToken
);
}
HttpClient integrates with observability out of the box and doesn't require Spring Cloud—unlike FeignClient, which has been in maintenance mode since 2022.
Integration with OpenAPI Generator
For automated client generation, use openapi-generator with the spring-http-interface library.
Maven plugin configuration:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator.version}</version>
<executions>
<execution>
<id>generate-weather-client</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi/external/weather-api.yaml</inputSpec>
<generatorName>spring</generatorName>
<library>spring-http-interface</library>
<output>${project.build.directory}/generated-sources/openapi-client</output>
<apiPackage>ulllie.exchange.openapi.gen.client.api</apiPackage>
<modelPackage>ulllie.exchange.openapi.gen.client.model</modelPackage>
<generateApis>true</generateApis>
<generateModels>true</generateModels>
<generateSupportingFiles>false</generateSupportingFiles>
<configOptions>
<useSpringBoot4>true</useSpringBoot4>
<useJackson3>true</useJackson3>
<interfaceOnly>true</interfaceOnly>
<skipDefaultInterface>true</skipDefaultInterface>
<useBeanValidation>true</useBeanValidation>
<annotationLibrary>none</annotationLibrary>
<serializationLibrary>jackson</serializationLibrary>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
This generates an interface like:
public interface OpenMeteoApi {
@HttpExchange(method = "GET", value = "/v1/forecast")
ResponseEntity<ForecastResponse> getForecast(
@RequestParam("latitude") Double latitude,
@RequestParam("longitude") Double longitude
);
}
Connecting and Configuring Services
Configuration via import:
@Configuration @ImportHttpServices(group = "weather", types = OpenMeteoApi.class)
public class WeatherRestClientConfig implements RestClientHttpServiceGroupConfigurer {
@Override
public void configureGroups(Groups<RestClient.Builder> groups) {
groups.filterByName("weather").forEachClient(
($, builder) -> builder.baseUrl("https://api.weather.com")
);
}
}
Error handling is configured via defaultStatusHandler:
builder.baseUrl(properties.baseUrl())
.defaultStatusHandler(
status -> status.is4xxClientError() || status.is5xxServerError(),
errorHandler
);
A custom HttpErrorHandler lets you tailor logic for 4xx/5xx status codes, integrate with Resilience4j, or use @ControllerAdvice.
Key features:
- Interceptor configuration
- Resilience4j integration
- ResponseSpec-level error handling
RestClient vs WebClient with Project Loom
With virtual threads (spring.threads.virtual.enabled=true), the blocking RestClient delivers I/O scalability without reactive programming.
Advantages:
- Imperative code that's easy to debug
- No reactive pipelines
- Performance on par with WebClient for I/O-bound tasks
For reactive scenarios, select WebClient.Builder to generate Mono/Flux signatures.
Validation Challenges and Solutions
Bean Validation doesn't recurse into nested objects because the generator skips @Valid.
Solutions:
- x-field-extra-annotation in OpenAPI:
@jakarta.validation.Valid— requires spec changes. - AlwaysTraversableResolver — traverses the entire object tree globally, but risks cycles and performance hits.
- Custom Mustache templates — auto-adds @Valid during generation.
Bonus: Generate Kotlin data classes for null-safety.
data class ForecastResponse(
val temperature: Double,
val windSpeed: Double?
)
Dependencies: kotlin-stdlib + jackson-module-kotlin. Fail-fast deserialization on nullability violations.
Key Takeaways
- HttpClient is native to Spring Framework—no Spring Cloud dependencies.
- OpenAPI generator with
spring-http-interfaceproduces ready-to-use interfaces. - Virtual threads simplify I/O without reactivity.
- Kotlin data classes catch null issues early.
- Error handling and validation are fully customizable.
— Editorial Team
No comments yet.