Cette page montre comment déclarer une interface client typée, l’injecter via CDI, et invoquer un service HTTP distant. Aucun proxy dynamique, aucune réflexion à chaud : la classe d’invocation est générée à la première utilisation par la Class-File API.

Pré-requis

  • Java 25 — Temurin recommandé. Module path activé.

  • Maven 3.9.16 — épinglé via .sdkmanrc à la racine du repo (sdk env).

  • JPMS strict — un module-info.java par module Maven.

  • Une implémentation JSON-B au classpath/module-path runtime — Champollion dans l’écosystème Vidocq.

  • Pour l’intégration CDI : Vauban côté container, Ravel pour MP Config.

Coordonnées Maven

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.vidocq.cyrano</groupId>
      <artifactId>cyrano-parent</artifactId>
      <version>0.1.0-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <!-- API publique : RestClientBuilder, annotations MP Rest Client, SPI Cyrano -->
  <dependency>
    <groupId>io.vidocq.cyrano</groupId>
    <artifactId>cyrano-api</artifactId>
  </dependency>

  <!-- Implémentation runtime : scanning, proxy Class-File API, transport HttpClient -->
  <dependency>
    <groupId>io.vidocq.cyrano</groupId>
    <artifactId>cyrano-core</artifactId>
    <scope>runtime</scope>
  </dependency>

  <!-- Intégration CDI optionnelle : BCE @RegisterRestClient + bean synthétique @RestClient -->
  <dependency>
    <groupId>io.vidocq.cyrano</groupId>
    <artifactId>cyrano-cdi-vauban</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>
cyrano-mp-rest-client-api est tiré en transitif. Ce module repackage la spec MicroProfile Rest Client (qui ne fournit pas de module-info ni d'`Automatic-Module-Name` upstream) pour produire un module JPMS explicite io.vidocq.cyrano.mp.rest.client.api. Sans lui, jlink échoue.

module-info.java

module com.example.client {
    requires io.vidocq.cyrano.api;
    requires io.vidocq.cyrano.mp.rest.client.api;
    requires jakarta.ws.rs;
    requires jakarta.cdi;

    // Pour permettre à JSON-B de désérialiser les DTO renvoyés
    opens com.example.client.dto to jakarta.json.bind;
}

Étape 1 — Décrire le service distant comme une interface

package com.example.client;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(configKey = "users-api")
@Path("/users")
public interface UsersClient {

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    UserDto findById(@PathParam("id") long id);
}

L’annotation @RegisterRestClient(configKey = "users-api") enregistre l’interface auprès de la BCE Cyrano et fournit la clé MP Config qui pilote l’URL de base.

Étape 2 — Configurer l’URL de base

Dans META-INF/microprofile-config.properties (résolu par Ravel) :

users-api/mp-rest/url=https://api.example.com
users-api/mp-rest/scope=jakarta.enterprise.context.ApplicationScoped
users-api/mp-rest/connectTimeout=2000
users-api/mp-rest/readTimeout=5000

La résolution suit la priorité MP Rest Client : <configKey>/mp-rest/uri > <configKey>/mp-rest/url > @RegisterRestClient(baseUri=…​).

Étape 3 — Injecter et appeler

package com.example.app;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import com.example.client.UsersClient;
import com.example.client.UserDto;

@ApplicationScoped
public class UserService {

    @Inject
    @RestClient
    UsersClient users;

    public String greet(long id) {
        UserDto user = users.findById(id);
        return "Bonjour " + user.name();
    }
}

À l’amorçage, la BCE CyranoRestClientExtension détecte UsersClient via @Enhancement(withAnnotations = RegisterRestClient.class), synthétise un bean qualifié @RestClient, et délègue l’instanciation à RestClientBuilder.newBuilder().baseUri(…​).build(UsersClient.class). À la première invocation, CyranoProxyGenerator émet Cyrano$UsersClient via la Class-File API.

Variante sans CDI — RestClientBuilder

Si l’application tourne en SE pur (sans CDI), construire le client à la main :

import java.net.URI;
import org.eclipse.microprofile.rest.client.RestClientBuilder;

UsersClient users = RestClientBuilder.newBuilder()
    .baseUri(URI.create("https://api.example.com"))
    .build(UsersClient.class);

UserDto user = users.findById(42L);

cyrano-core se suffit à lui seul pour ce mode — cyrano-cdi-vauban n’est pas requis.

Étape 4 — Lancer

cd cyrano
sdk env
./mvnw -ntp install -DskipTests
./mvnw test

Pour exécuter le TCK officiel MicroProfile Rest Client 4.0, voir la page TCK (commande ./run-official-tck-mp-rest-client-4.0.sh).

Et après ?

  • Concepts — vocabulaire MP Rest Client.

  • Cas d’usage — async, headers dynamiques, exception mappers, providers.

  • Référence — toutes les clés MP Config supportées.