JAX-RS: Java API for RESTful Web Services
JAX-RS (Java API for RESTful Web Services) is a specification that provides a standardized, annotation-driven approach to building REST APIs in Java. Part of the Jakarta EE ecosystem (formerly Java EE), JAX-RS lets developers expose Java methods as HTTP endpoints with minimal boilerplate.
Popular implementations include Jersey (the reference implementation), RESTEasy, and Apache CXF.
Why JAX-RS?
Before JAX-RS, building REST services in Java often required manually parsing requests, mapping URLs, and serializing responses. JAX-RS abstracts these concerns behind a clean, declarative annotation model, allowing you to focus on business logic.
Key benefits include:
- Annotation-based routing for concise endpoint definitions
- Automatic content negotiation (JSON, XML, etc.)
- Pluggable providers for serialization and exception handling
- Portability across compliant implementations
Core Annotations
JAX-RS revolves around a small set of annotations. Here are the essentials:
| Annotation | Purpose |
|---|---|
@Path |
Defines the URI path for a resource |
@GET, @POST, @PUT, @DELETE
|
HTTP method binding |
@Produces |
Specifies response media type |
@Consumes |
Specifies accepted request media type |
@PathParam |
Extracts values from the URI path |
@QueryParam |
Extracts values from query parameters |
A Simple Resource
Let's build a basic resource that manages 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<>();
@GET
public Collection<Book> listBooks() {
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) {
BOOKS.put(book.getId(), book);
return Response.status(Response.Status.CREATED)
.entity(book)
.build();
}
@DELETE
@Path("/{id}")
public Response deleteBook(@PathParam("id") int id) {
Book removed = BOOKS.remove(id);
return removed == null
? Response.status(Response.Status.NOT_FOUND).build()
: Response.noContent().build();
}
}
Registering the Application
To bootstrap your JAX-RS application, extend Application and define the base path:
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class RestApplication extends Application {
// Resources are auto-discovered via classpath scanning
// in most modern implementations.
}
With this configuration, the book endpoints are available under /api/books.
Working with Parameters
JAX-RS makes it easy to bind different parts of a request to method parameters:
@GET
@Path("/search")
public List<Book> search(
@QueryParam("author") String author,
@QueryParam("limit") @DefaultValue("10") int limit,
@HeaderParam("X-Request-Id") String requestId) {
// Filtering logic here
return findByAuthor(author, limit);
}
The @DefaultValue annotation provides a fallback when a parameter is absent, avoiding null checks.
Exception Handling
Rather than scattering try/catch blocks throughout your resources, use an ExceptionMapper to centralize error handling:
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class NotFoundMapper implements ExceptionMapper<ResourceNotFoundException> {
@Override
public Response toResponse(ResourceNotFoundException ex) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", ex.getMessage()))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
This keeps resource methods clean and ensures consistent error responses across the API.
Content Negotiation
JAX-RS supports serving multiple representations of a resource. By listing several media types, the framework selects the best match based on the client's Accept header:
@GET
@Path("/{id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Book getBook(@PathParam("id") int id) {
return BOOKS.get(id);
}
Best Practices
-
Use appropriate status codes — return
201 Created,204 No Content, and404 Not Foundwhere relevant. - Keep resources thin — delegate business logic to service classes.
- Leverage DTOs — avoid exposing internal entities directly.
Top comments (0)