JAX-RS: Java API for RESTful Web Services
JAX-RS (Java API for RESTful Web Services) is a specification that simplifies the development of REST APIs in Java. It provides a set of annotations and interfaces that let developers expose plain Java objects as web resources with minimal boilerplate. Popular implementations include Jersey (the reference implementation), RESTEasy, and Apache CXF.
In this post, we'll explore the core concepts of JAX-RS and build a practical example.
Why JAX-RS?
Before JAX-RS, building REST services in Java often meant working directly with servlets and manually parsing requests. JAX-RS abstracts away this complexity by:
- Mapping HTTP methods to Java methods via annotations
- Handling content negotiation automatically
- Serializing and deserializing objects (JSON, XML)
- Providing clean URI templating and parameter injection
Setting Up Dependencies
If you're using Maven with Jersey, add the following to your pom.xml:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>3.1.3</version>
</dependency>
Core Annotations
JAX-RS revolves around a handful of key annotations:
| Annotation | Purpose |
|---|---|
@Path |
Defines the URI path for a resource |
@GET, @POST, @PUT, @DELETE
|
Bind methods to HTTP verbs |
@Produces |
Declares the media type returned |
@Consumes |
Declares the media type accepted |
@PathParam |
Injects a value from the URI path |
@QueryParam |
Injects a query string parameter |
Building a Resource Class
Let's create a simple resource for managing books:
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.*;
@Path("/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
private static final Map<Integer, Book> BOOKS = new HashMap<>();
private static int counter = 1;
@GET
public Collection<Book> getAllBooks() {
return BOOKS.values();
}
@GET
@Path("/{id}")
public Response getBook(@PathParam("id") int id) {
Book book = BOOKS.get(id);
if (book == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(book).build();
}
@POST
public Response createBook(Book book) {
int id = counter++;
book.setId(id);
BOOKS.put(id, book);
return Response.status(Response.Status.CREATED).entity(book).build();
}
@DELETE
@Path("/{id}")
public Response deleteBook(@PathParam("id") int id) {
if (BOOKS.remove(id) == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.noContent().build();
}
}
The Book class is a plain POJO:
public class Book {
private int id;
private String title;
private String author;
// getters and setters omitted for brevity
}
Registering the Application
To bootstrap your JAX-RS application, extend Application and register your resources:
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import java.util.Set;
@ApplicationPath("/api")
public class RestApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return Set.of(BookResource.class);
}
}
With this configuration, your endpoints are available under /api/books.
Working with Query Parameters
Query parameters are useful for filtering and pagination:
@GET
@Path("/search")
public Collection<Book> searchBooks(
@QueryParam("author") String author,
@QueryParam("limit") @DefaultValue("10") int limit) {
return BOOKS.values().stream()
.filter(b -> author == null || author.equals(b.getAuthor()))
.limit(limit)
.toList();
}
The @DefaultValue annotation ensures a sensible fallback when the parameter is absent.
Exception Handling
Rather than scattering try-catch blocks everywhere, use an ExceptionMapper to centralize error handling:
java
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", ex.getMessage()))
.build();
}
Top comments (0)