Retour à l'accueil

HttpClient Spring 7 : remplacement de Feign

Spring 7 introduit HttpClient déclaratif comme alternative native à FeignClient. Le support du générateur OpenAPI, des threads virtuels et de la null-safety Kotlin simplifie l'intégration d'API externes. Détails sur la configuration, la validation et la gestion des erreurs.

Spring 7 HttpClient : guide complet pour remplacer Feign
Advertisement 728x90

HttpClient déclaratif de Spring 7 : l'alternative à FeignClient

Spring 7 introduit un HttpClient déclaratif natif basé sur RestClient et WebClient. Il utilise des annotations familières comme @GetExchange et @PostExchange, similaires à @GetMapping dans les contrôleurs. Cela permet de définir des requêtes HTTP sortantes via des interfaces, sans implémentation manuelle.

Voici à quoi ressemble l'interface principale :

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 s'intègre nativement à l'observabilité et ne nécessite pas Spring Cloud — contrairement à FeignClient, en mode maintenance depuis 2022.

Google AdInline article slot

Intégration avec OpenAPI Generator

Pour générer automatiquement les clients, utilisez openapi-generator avec la bibliothèque spring-http-interface.

Configuration du plugin Maven :

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

Cela génère une interface comme :

Google AdInline article slot
public interface OpenMeteoApi { 
  @HttpExchange(method = "GET", value = "/v1/forecast")   
  ResponseEntity<ForecastResponse> getForecast( 
    @RequestParam("latitude") Double latitude, 
    @RequestParam("longitude") Double longitude 
  );
}

Connexion et configuration des 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") 
    );    
  }  
}

La gestion d'erreurs se configure via defaultStatusHandler :

builder.baseUrl(properties.baseUrl())
  .defaultStatusHandler(
  status -> status.is4xxClientError() || status.is5xxServerError(),               
  errorHandler         
);

Un HttpErrorHandler personnalisé permet d'adapter la logique pour les codes 4xx/5xx, d'intégrer Resilience4j ou d'utiliser @ControllerAdvice.

Google AdInline article slot

Fonctionnalités clés :

  • Configuration d'intercepteurs
  • Intégration Resilience4j
  • Gestion d'erreurs au niveau ResponseSpec

RestClient vs WebClient avec Project Loom

Avec les threads virtuels (spring.threads.virtual.enabled=true), le RestClient bloquant offre une scalabilité I/O sans programmation réactive.

Avantages :

  • Code impératif facile à déboguer
  • Pas de pipelines réactifs
  • Performances équivalentes à WebClient pour les tâches I/O

Pour les scénarios réactifs, optez pour WebClient.Builder afin de générer des signatures Mono/Flux.

Défis de validation et solutions

Bean Validation ne parcourt pas récursivement les objets imbriqués car le générateur omet @Valid.

Solutions :

  • x-field-extra-annotation dans OpenAPI : @jakarta.validation.Valid — nécessite des modifications de spécification.
  • AlwaysTraversableResolver — parcourt l'arbre d'objets en entier globalement, mais risque des cycles et pertes de performance.
  • Modèles Mustache personnalisés — ajoute automatiquement @Valid lors de la génération.

Bonus : Générez des data classes Kotlin pour la sécurité null.

data class ForecastResponse(
  val temperature: Double,
  val windSpeed: Double?
)

Dépendances : kotlin-stdlib + jackson-module-kotlin. Détection rapide des erreurs de nullabilité lors de la désérialisation.

Points clés à retenir

  • HttpClient est natif au Spring Framework — sans dépendances Spring Cloud.
  • OpenAPI generator avec spring-http-interface produit des interfaces prêtes à l'emploi.
  • Les threads virtuels simplifient l'I/O sans réactivité.
  • Les data classes Kotlin détectent tôt les problèmes de null.
  • Gestion d'erreurs et validation entièrement personnalisables.

— Editorial Team

Advertisement 728x90

Lire ensuite