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.
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>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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.