Backend Development 12 min read

Mastering JAX‑RS Client API in Spring Boot 3: From Basics to Advanced Async Calls

This tutorial demonstrates how to use JAX‑RS Client API within a Spring Boot 3.2.5 application to define REST resources, compare it with Spring's RestTemplate and WebClient, and implement synchronous, asynchronous, and reactive remote calls with custom providers, filters, and advanced configurations.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering JAX‑RS Client API in Spring Boot 3: From Basics to Advanced Async Calls

1. Introduction

JAX‑RS (Java API for RESTful Web Services) is the Jakarta EE standard for building and consuming RESTful services. The article explains how to use its Client API in a Spring Boot 3.2.5 project to call third‑party APIs.

2. Resource Definition Example

<code>import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@Path("/users")
public class UserResource {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getUsers() {}

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void createUser(User user) {}

    @PUT
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    public void updateUser(@PathParam("id") int id, User user) {}

    @DELETE
    @Path("/{id}")
    public void deleteUser(@PathParam("id") int id) {}
}</code>

3. Why Choose JAX‑RS?

Standardization and Compatibility : As a Jakarta EE standard, JAX‑RS works consistently across implementations such as Jersey or RESTEasy.

Ease of Use and Flexibility : Provides a concise API for building complex HTTP requests and supports multiple HTTP methods.

Asynchronous Support : Native async request handling improves performance for long‑running tasks.

4. Preparing a Sample Third‑Party Service

<code>@GetMapping("/{id}")
public User queryUser(@PathVariable Long id) {
    return new User(id, "Name - " + new Random().nextInt(100000));
}

@GetMapping("/header")
public String header(@RequestHeader("x-token") String token) {
    return token;
}

@GetMapping("/exception")
public User exception(Long id, String name) {
    System.out.println(1 / 0);
    return new User(id, name);
}

@GetMapping("")
public List<User> queryUsers() throws Exception {
    TimeUnit.SECONDS.sleep(2);
    return List.of(
        new User(1L, "Name - " + new Random().nextInt(100000)),
        new User(2L, "Name - " + new Random().nextInt(100000)),
        new User(3L, "Name - " + new Random().nextInt(100000))
    );
}</code>

5. Basic Client Call

<code>Response response = ClientBuilder.newClient()
    .target("http://localhost:9100/users/666")
    .request()
    .get();
String ret = response.readEntity(String.class);
System.out.println(ret);</code>

Output: {"id":666,"name":"Name - 57414"}

6. Registering a Provider (MessageBodyReader)

<code>public class JSONToObjectReader implements MessageBodyReader<Object> {
    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return !type.isPrimitive();
    }
    @Override
    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
                           MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
            throws IOException, WebApplicationException {
        return new ObjectMapper().readValue(entityStream, type);
    }
}</code>

Register the provider:

<code>Response response = ClientBuilder.newClient()
    .register(JSONToObjectReader.class)
    .target("http://localhost:9100/users/666")
    .request()
    .get();
User user = response.readEntity(User.class);
System.out.println(user);
</code>

7. Adding a Filter

<code>public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        logger.info("Sending request, headers: {}", requestContext.getHeaders());
        requestContext.getHeaders().replace("x-token", List.of("88888888888"));
    }
    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        logger.info("Received response, headers: {}", responseContext.getHeaders());
    }
}</code>

Register the filter the same way as the provider.

8. Controlling Component Order

<code>@Priority(-1)
public class AuthFilter implements ClientRequestFilter, ClientResponseFilter {}

@Priority(0)
public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter {}
</code>

9. Asynchronous Calls

<code>int core = Runtime.getRuntime().availableProcessors();
ExecutorService executor = new ThreadPoolExecutor(core, core, 60, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(100));
Client client = ClientBuilder.newBuilder().executorService(executor)
    .register(JSONToObjectReader.class).build();
Future<User> future = client.target("http://localhost:9100/users/666")
    .request(MediaType.APPLICATION_JSON)
    .async()
    .get(new InvocationCallback<User>() {
        public void completed(User response) {
            System.out.printf("%s - request completed: %s%n", Thread.currentThread().getName(), response);
        }
        public void failed(Throwable throwable) {
            System.err.printf("Request failed: %s%n", throwable.getMessage());
        }
    });
User ret = future.get();
System.out.printf("Result: %s%n", ret);
</code>

10. Reactive Support with CompletionStage

<code>CompletionStage<User> stage = ClientBuilder.newClient()
    .target("http://localhost:9100/users/666")
    .register(JSONToObjectReader.class)
    .request(MediaType.APPLICATION_JSON)
    .rx()
    .get(User.class);
stage.whenComplete((res, ex) -> {
    if (ex != null) {
        System.err.printf("Error: %s%n", ex.getMessage());
    } else {
        System.out.println(res);
    }
});
</code>

11. Advanced Configuration (Timeouts, Global Provider)

<code>ClientBuilder builder = ClientBuilder.newBuilder();
builder.connectTimeout(1000, TimeUnit.MILLISECONDS);
builder.readTimeout(1000, TimeUnit.MILLISECONDS);
builder.register(JSONToObjectReader.class);
Client client = builder.build();
client.target("http://localhost:9100/users")
    .request(MediaType.APPLICATION_JSON)
    .buildGet()
    .submit(new InvocationCallback<List<User>>() {
        public void completed(List<User> response) {
            System.out.printf("Result: %s%n", response);
        }
        public void failed(Throwable throwable) {
            System.err.printf("Error: %s%n", throwable.getMessage());
        }
    });
</code>
javaBackend DevelopmentREST clientJAX-RSSpring Boot 3
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.