DEV Community

Said Olano
Said Olano

Posted on

JAX-RS: Building RESTful Web Services in Java (2026-08-20 18:44)

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use appropriate status codes — return 201 Created, 204 No Content, and 404 Not Found where relevant.
  • Keep resources thin — delegate business logic to service classes.
  • Leverage DTOs — avoid exposing internal entities directly.

Top comments (0)