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

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) {}
}

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

@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))
    );
}

5. Basic Client Call

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

Output:

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

6. Registering a Provider (MessageBodyReader)

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);
    }
}

Register the provider:

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

7. Adding a Filter

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());
    }
}

Register the filter the same way as the provider.

8. Controlling Component Order

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

@Priority(0)
public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter {}

9. Asynchronous Calls

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

10. Reactive Support with CompletionStage

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);
    }
});

11. Advanced Configuration (Timeouts, Global Provider)

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());
        }
    });
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.