Skip to content

REST clients

View Markdown

vertique-rest-client is Vertique’s declarative outbound HTTP client. It uses familiar JAX-RS annotations on a Java interface, but it is not jakarta.ws.rs.client and does not implement a JAX-RS client container. Each invocation becomes an asynchronous HTTP request over Vert.x WebClient and returns a Future result.

Use this page when your application calls a known HTTP service. Use REST APIs for inbound JAX-RS resources, and JAX-RS compatibility for the server-side compatibility boundary.

By the end of this page you will have a typed client interface, know how to build or inject its proxy, and understand where outbound resilience and configuration are resolved.

Annotate one Java interface per upstream service. @RestClient gives the client a logical name and an optional default base URL. JAX-RS annotations describe the HTTP operation and its parameters. Methods return a Future result so the call stays asynchronous.

This excerpt is from the public UserClient.java example. The interface-level circuit breaker applies to all of its methods unless a method overrides it:

@RestClient(name = "userService", value = "http://localhost:8081")
@Path("/api/users")
@Produces("application/json")
@Consumes("application/json")
@CircuitBreaker(maxFailures = 5, timeoutMs = 5000)
public interface UserClient {
@GET
Future<List<User>> list(@QueryParam("page") int page);
@GET
@Path("/{id}")
Future<Optional<User>> findById(@PathParam("id") String id);
@POST
@ExpectedStatus(201)
Future<User> create(User user);
@DELETE
@Path("/{id}")
@ExpectedStatus(204)
Future<Void> delete(@PathParam("id") String id);
}

The client name is also the key used for external configuration. Method annotations override interface defaults where the client supports both levels. The client shares Vertique’s parameter conversion and JSON serialization infrastructure with the server runtime, but the request dispatch, connection pool, and lifecycle are client-specific.

For a standalone client, create a builder, set the base URL and transport defaults, and build the typed proxy. This is the canonical builder shape from the vertique-rest-client module reference:

UserClient client = RestClientBuilder.create(vertx)
.baseUrl("http://user-service:8080")
.readTimeout(5, TimeUnit.SECONDS)
.build(UserClient.class);

The builder scans and validates the interface, resolves converters and JSON configuration, creates the client context, and returns the proxy during build(). A missing base URL, an unsupported parameter converter, or an unknown configured profile fails during build(), before the first call.

In a Dagger application, add RestClientModule and the generated GeneratedRestClientsModule to the component. The generated module contributes the binding for each @RestClient interface, while RestClientModule provides the factory and shared client infrastructure. The public example’s AppComponent shows the complete composition.

The declarative client owns the outbound call boundary, so inline resilience annotations activate directly on the client interface or method. Use @Timeout, @Retry, @CircuitBreaker, or @Bulkhead at interface level for a default shared by its methods, or at method level for a method-specific override. Unlike a concrete resource or service bean, a client interface does not need @Resilient for inline declarations.

Use @Resilient(policy = "payments") on a client method when a Dagger-managed client should select a named policy tier. Install ResiliencePoliciesModule so the named tier is available. The client-level restClient.{name}.retry configuration is a higher-precedence transport override. Standalone builders have no named-policy registry. An unknown named policy fails while the client is built.

The runtime creates one resilience pipeline per client method. A retry is a new physical HTTP attempt, while a bulkhead permit covers the complete logical call, including its retries. Keep retry declarations for operations that are replay-safe or idempotent. A resource boundary should normally enforce only the bounded timeout described in REST APIs.

External configuration is read from the restClient section. The client name comes from @RestClient(name = "..."), or from the interface’s simple name when name is blank. Configuration keys include:

  • restClient.{name}.baseUrl: overrides the annotation and builder base URL.
  • restClient.{name}.readTimeoutMs: per-request timeout in milliseconds.
  • restClient.{name}.retry.maxRetries and backoffStrategy: client-level retry overrides.
  • restClient.{name}.circuitBreaker.*: circuit-breaker settings.
  • restClient.{name}.webClient.* and pool.*: transport and connection-pool settings.
  • restClient.defaults.jsonProfile: a boundary-wide JSON mapper profile default.

External configuration has the highest priority. Method annotations then override interface defaults, followed by builder settings and framework defaults. Duration keys in the external configuration use explicit units, such as readTimeoutMs. Do not copy raw Vert.x duration names into the application configuration. The complete key table and precedence matrix are in the vertique-rest-client module reference.

Standalone builders own the resilience runtime they create and should be closed when the client lifetime ends. Builders created with an application-supplied runtime, and builders returned by RestClientFactory, do not own that application runtime. Closing them releases only their client contexts and WebClient instances. close() is deterministic and idempotent.

Client configuration and interface metadata are resolved at build time. Transport failures, unexpected status responses, conversion errors, and resilience rejections propagate through the REST-client failure hierarchy. They do not become inbound JAX-RS Response objects automatically. Choose an application-level error policy at the call site or in a client interceptor.

  • vertique-rest-client module reference: complete annotations, request pipeline, configuration, failure hierarchy, and lifecycle.
  • Configuration: define a named resilience policy tier for Dagger-managed clients.
  • JAX-RS compatibility: why this client is separate from jakarta.ws.rs.client compatibility.
  • REST APIs: define inbound JAX-RS resources and bound their request-side work.
  • Services: use generated event-bus-backed clients for internal service contracts.