Disclaimer: While I usually reword several pieces of the content I take as notes from books like this one, many other pieces are just literal texts extracted from the book(s). The authors of those literal texts extracted from the books are the only owners of such content. If you're the co/author, publisher, or, in general, a copyright owner of this book or related material and consider the content of this website (https://lealceldeiro.com/) doesn't comply with your work's copyright policy, please get in touch .


Main notes taken from the book RESTful Java with JAX-RS 2.0, 2nd Edition by Bill Burke. Copyright 2014 Bill Burke, 978-1-449-36134-1.

Source code at: https://github.com/oreillymedia/restful_java_jax-rs_2_0

Note: As this is rather an old book, it's advisable to always verify if there are newer, more modern alternatives for solutions explained here; for instance, when Java clients or options to secure Java application are mentioned.


Java API for RESTful web services (JAX-RS) aims to make development of RESTful web services in Java simple and intuitive.

Chapter 1: Introduction to REST

Architectural principles of the REpresentational State Transfer (REST):

  • Addressable resources

  • A uniform, constrained interface

  • Representation-oriented

  • Communicate statelessly

  • Hypermedia As The Engine Of Application State (HATEOAS)

Example of an HTTP request performed by the browser:

GET /resteasy HTTP/1.1
Host: jboss.org
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language:
en-us,en;q=0.5
Accept-Encoding:
gzip,deflate

To the previous request, the response could be:

HTTP/1.1 200 OK
X-Powered-By: Servlet 2.4; JBoss-4.2.2.GA
Content-Type: text/html
<head>
<title>JBoss RESTEasy Project</title>
</head>
<body>
<h1>JBoss RESTEasy</h1>
<p>JBoss RESTEasy is an open source implementation of the JAX-RS specification...

URI characters are encoded following these rules:

  • The characters a-z, A-Z, 0-9, ., -, *, and _ remain the same

  • The space character is converted to +

  • The other characters are first converted into a sequence of bytes using a specific encoding scheme. Next, a two-digit hexadecimal number prefixed by % represents each byte

GET is a read-only operation. It is used to query the server for specific information. It is both an idempotent and safe operation.

PUT requests that the server store the message body sent with the request under the location provided in the HTTP message. It is usually modeled as an insert or update and it’s also idempotent.

DELETE is used to remove resources. It is idempotent as well.

POST is non-idempotent and unsafe operation. Each POST method is allowed to modify the service in a unique way. We may or may not send information with the request. We may or may not receive information from the response.

HEAD is exactly like GET except that instead of returning a response body, it returns only a response code and any headers associated with the request.

OPTIONS is used to request information about the communication options of the resource we’re interested in. It allows the client to determine the capabilities of a server and a resource without triggering any resource action or retrieval.

In a RESTful system, the complexity of the client-server interaction is within the representations being passed back and forth. These representations could be XML, JSON, YAML, or any other format designed for this purpose.

HTTP uses the Content-Type header to tell the client or server what data format it is receiving. Its value string is in the Multipurpose Internet Mail Extension (MIME) format. The MIME format is basic: type/subtype;name=value;name=value…​. For example:

text/plain
text/html
application/xml
text/html; charset=iso-8859-1

Chapter 2: Designing RESTful Services

PUT and POST are required by their specification to send a response code of 201, “Created,” if a new resource was created on the server as a result of the request.

The Location header in the response message provides a URI to the client for a created resource, so it knows where to further interact with the resource that was just created (i.e., if the client wanted to update the resource).

For DELETE, the HTTP specification requires that we send a response code of 200 OK, and a response message body or a response code of 204 No Content, without any response body.

Chapter 3: First JAX-RS Service

JAX-RS is a framework that focuses on applying Java annotations to plain Java objects. It has annotations to bind specific URI patterns and HTTP operations to individual methods in the Java classes.

A JAX-RS service is a Java class that uses JAX-RS annotations to bind and map specific incoming HTTP requests to Java methods that can service these requests.

In vanilla JAX-RS, services can either be singletons or per-request objects. A singleton means that one and only one Java object services HTTP requests. Per-request means that a Java object is created to process each incoming request and is thrown away at the end of that request.

Example:

import jakarta.ws.rs.Path;

@Path("/resource-uri")
public class ResourceService {
    // ... logic omitted for brevity
}

@Path, under package jakarta.ws.rs, was formerly under the package javax.ws.rs. See URI Templates and Changes since version 2.1 for more info.

In general packages javax.* are now jakarta.*.

Example of a service to create a resource:

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import java.io.InputStream;

@POST
@Consumes("application/xml")
public Response createResource(InputStream is) {
    // ... code omitted for brevity
}

Example of a service to get a resource:

import jakarta.ws.rs.core.StreamingOutput;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;

@GET
@Path("{id}")
@Produces("application/xml")
public StreamingOutput getResource(@PathParam("id") int id) {
    // code omitted for brevity
}
In general, we don’t use the StreamingOutput interface to output responses. Instead, we would use some of the content handlers provided by JAX-RS that can automatically convert Java objects straight into the data format being sent across the wire.

Example of a service to update a resource:

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.PathParam;
import java.io.InputStream;

@PUT
@Path("{id}")
@Consumes("application/xml")
public void updateResource(@PathParam("id") int id, InputStream is) {
    // code omitted for brevity
}

In the previous example, IntputSteram is is not annotated with a JAX-RS annotation, so it’s considered a representation of the body of the incoming message.

An alternative implementation to having the JAX-RS annotations applied directly to the methods than handle the business logic, is to have an interface where the methods are defined along with the annotations. Then a class that implements the interface would implement the methods defined by the interface, without needing to have the annotations applied to the methods in the concrete class.

In case it’s needed, any of the JAX-RS annotations applied to the interface can be applied again to the methods in the concrete class (and re-define the annotation configuration). Although, re-applying annotation on the concrete class is not advisable because the code is harder to reason about and maintain.

Chapter 4: HTTP Method and URI Matching

The full ist of JAX-RS annotations that map to specific HTTP operations are:

  • @jakarta.ws.rs.GET

  • @jakarta.ws.rs.PUT

  • @jakarta.ws.rs.POST

  • @jakarta.ws.rs.DELETE

  • @jakarta.ws.rs.HEAD

These annotations by themselves don’t mean anything to JAX-RS. Instead, the framework looks at the meta-annotation @jakarta.ws.rs.HttpMethod applied to them; that’s how they’re picked up and used to map the HTTP operation to the methods they annotate.

The value of the @Path annotation is an expression that denotes a relative URI to the context root of the JAX-RS application.

The JAX-RS specification has defined strict sorting and precedence rules for matching URI expressions and is based on a most specific match wins algorithm.

The JAX-RS provider gathers the deployed URI expressions and sorts them based on the following logic:

  1. The primary key of the sort is the number of literal characters in the full URI matching pattern. The sort is in descending order.

  2. The secondary key of the sort is the number of template expressions embedded within the pattern, that is, i.e., {id} or {id : .+}. This sort is also in descending order.

  3. The tertiary key of the sort is the number of non-default template expressions. A default template expression is one that doesn’t define a regular expression, that is, i.e., {id}.

Example of URIs, sorted by this logic:

/resource/{id}/{name}/address   (1)
/resource/{id : .+}/address     (2)
/resource/{id}/address          (3)
/resource/{id : .+}             (4)
1 Expressions 1 to 3 all have the same number of literal characters, but expression 1 comes first because of the second sorting rule: it has more template expressions embedded within the pattern.
2 Expression 2 is sorted ahead of 3 because of the third sorting rule; it has a template pattern that is a regular expression while expression 3 doesn’t have one.
3 Expression 3 is sorted ahead of expression 4 because it has more literal characters tan expression 4.
4 Expressions 1 to 3 come first because they all have more literal characters than expression 4.

Allowable and reserved characters:

  • The US-ASCII alphabetic characters a-z and A-Z are allowable.

  • The decimal digit characters 0-9 are allowable.

  • All these other characters are allowable: _-!.~'()*.

  • These characters are allowed but are reserved for URI syntax: ,;:$&+=?/\[]@.

All other characters must be encoded using the % character followed by a two-digit hexadecimal number. This hexadecimal number corresponds to the equivalent hexadecimal character in the ASCII table.

Matrix parameters are name-value pairs embedded within the path of a URI string. Example:

https://domain.com/resources/r1;color=black/333

They come after a URI segment and are delimited by the ;. In the previous example that’d be color=black/333. The name is color and the value is black.

Matrix parameters are different from query parameters, as they represent attributes of certain URI segments and are used for identification purposes. They could be seen as adjectives. Query parameters, on the other hand, always come at the end of the URI and always pertain to the full resource we’re referencing. They’re ignored when matching incoming requests to JAX-RS resource methods, and it’s illegal to specify them within an @Path expression.

JAX-RS also allows us to dynamically dispatch requests through subresource locators. These are Java methods annotated with @Path, but with no HTTP method annotation, like @GET, applied to them. These methods return an object that is itself a JAX-RS annotated service that knows how to dispatch the remainder of the request.

Example:

// imports omitted for brevity

@Path("/resource")
public class RootResource {                                         // (1)
    @Path("{id}-x")                                                 // (2)
    public ResourceX getX(@PathParam("id") String xId) {
        return locateResourceX(xId);                                (3)
    }

    protected ResourceX locateResourceX(String xId) {
        // code omitted for brevity
    }
}
1 The RootResource class is our root resource, and it doesn’t service any HTTP requests directly, it serves as a locator.
2 It processes the x identifier part of the URI (id)
3 Then it locates the identified x resource and returns an instance of the ResourceX

The JAX-RS provider uses this ResourceX instance to service the remainder of the request:

// imports omitted for brevity

// no need to annotate this resource with `@Path` as the locator will create an instance of it
// and the JAX-RS framework will know how to handle the rest of the request processing
public class ResourceX {
    private Map<Integer, Customer> xResources;
    private AtomicInteger idCounter = new AtomicInteger();

    public ResourceX(Map<Integer, Customer> xResources) {
        this.xResources = xResources;
    }

    @POST
    @Consumes("application/xml")
    public Response createResource(InputStream is) {
        // code omitted for brevity
    }

    @GET
    @Path("{id}")
    @Produces("application/xml")
    public StreamingOutput getResource(@PathParam("id") int id) {
        // code omitted for brevity
    }

    @PUT
    @Path("{id}")
    @Consumes("application/xml")
    public void updateResource(@PathParam("id") int id, InputStream is) {
        // code omitted for brevity
    }
}

Following along with the previous example: if a client sends GET /resource/blue-x/333, the JAX-RS provider will first match the expression on the method RootResource.getX. Then, it will match and process the remaining part of the request with the method ResourceX.getResource().

JAX-RS provides an even more flexible full dynamic dispatching, where the located subresource can be "dynamically" defined in the root resource. Following along with the previous example, we could evolve it as follows:

// imports omitted for brevity

@Path("/resource")
public class RootResource {
    @Path("{id}-x")
    public IResourceX getX(@PathParam("id") String xId) {           // (1)
        return locateResourceX(xId);
    }

    protected IResourceX locateResourceX(String xId) {
        if (/*some condition*/) {
            return locateResourceX1(xId);
        }
        return locateResourceX2(xId);
    }

    private ResourceX1 locateResourceX1(String xId) {
        // code omitted for brevity
    }

    private ResourceX2 locateResourceX2(String xId) {
        // code omitted for brevity
    }
}

interface IResourceX {
    // code omitted for brevity
}
class ResourceX1 implements IResourceX {
    // code omitted for brevity
}
class ResourceX2 implements IResourceX {
    // code omitted for brevity
}
1 Now, instead of a specific resource (concrete class), we return an interface. JAX-RS will introspect the instance returned to figure out how to dispatch the request.
If there is at least one other resource method whose @Path expression matches, then no subresource locator will be traversed to match the request.

Chapter 5: JAX-RS Injection

JAX-RS annotations that can be used to inject values from the HTTP request into the Java method:

  • @jakarta.ws.rs.PathParam: Extracts values from URI template parameters. This is also able to inject instances of jakarta.ws.rs.core.PathSegment, which is an abstraction of a specific URI path segment.

  • @jakarta.ws.rs.MatrixParam: Extracts values from URI matrix parameters.

  • @jakarta.ws.rs.QueryParam: Extracts values from URI query parameters.

  • @jakarta.ws.rs.FormParam: Extracts values from posted form data.

  • @jakarta.ws.rs.HeaderParam: Extracts values from HTTP request headers.

  • @jakarta.ws.rs.CookieParam: Extracts values from HTTP cookies set by the client.

  • @jakarta.ws.rs.core.Context: The all-purpose injection annotation. It injects various helpers and informational objects that are provided by the JAX-RS API.

The more interesting method in PathSegment is getMatrixParameters(). It returns a map with all the matrix parameters applied to a particular URI segment. In combination with @PathParam, we can access the matrix parameters applied to the request’s URI.

When we need a more general raw API to query and browse information about the incoming request’s URI, the interface jakarta.ws.rs.core.UriInfo provides such an API. It’s instance can be acquired by using the @jakarta.ws.rs.core.Context injection annotation.

jakarta.ws.rs.BeanParam was added in the JAX-RS 2.0 specification. It injects an application-specific class whose property methods or fields are annotated with any of the other injection parameters (such as @FormParam, @HeaderParam, etc).

Example:

// imports omitted for brevity

class CustomInput {
    @FormParam("first")
    String firstName;

    @FormParam("list")
    String lastName;

    @HeaderParam("Content-Type")
    String contentType;

    // code omitted for brevity... getters... setters
}

@Path("/customers")
class CustomerResource {
    @POST
    public void createCustomer(@BeanParam CustomInput customInput) {
        // code omitted for brevity
    }
}

JAX-RS can convert the string data extracted by the previous annotations from the HTTP request (URI) into any Java type, provided that it matches one of the following criteria:

  1. It is a primitive type. The int, short, float, double, byte, char, and boolean types all fit into this category.

  2. It is a Java class that has a constructor with a single String parameter.

  3. It is a Java class that has a static method named valueOf() that takes a single String argument and returns an instance of the class.

  4. It is a java.util.List<T>, java.util.Set<T>, or java.util.SortedSet<T>, where T is a type that satisfies criteria 2 or 3 or is a String. Examples are List<Double>, Set<String>, or SortedSet<Integer>.

Additionally, in scenarios where automatic conversion is not possible, JAX-RS (from version 2.0 onwards) provides the jakarta.ws.rs.ext.ParamConverter<T> interface to help with parameter conversions.

Example:

// imports omitted for brevity

public enum Color {
    BLACK, BLUE, RED, WHITE, SILVER
}
// ...
public class ColorConverter implements ParamConverter<Color> {
    public Color fromString(String value) {
        return java.util.Arrays.stream(Color.values())
                               .filter(color -> color.toString().equalsIgnoreCase(value))
                               .findAny()
                               .orElseThrow(() -> new IllegalArgumentException("Invalid color: " + value));
    }
    public String toString(Color value) { return value.toString(); }
}
// ...
@Provider
public class ColorConverterProvider {
    private final ColorConverter converter = new ColorConverter();

    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        return rawType.equals(Color.class) ? converter : null;
    }
}

If the JAX-RS provider fails to convert a string into the Java type specified, it is considered a client error. If this failure happens during the processing of an injection for an @MatrixParam, @QueryParam, or @PathParam, an error status of 404 Not Found is sent back to the client. If the failure happens with @HeaderParam or @CookieParam, an error response code of 400 Bad Request is sent.

The @jakarta.ws.rs.Encoded annotation allows us to work with the raw-encoded values from the HTTP request, as opposite to working with the decoded values, as extracted by the other annotations such as @QueryParam.

Chapter 6: JAX-RS Content Handlers

Built-in content marshalling handlers:

  • jakarta.ws.rs.core.StreamingOutput is a simple callback interface that can be implemented to do raw streaming of response bodies.

  • java.io.InputStream and java.io.Reader can be used to read request message bodies and inputting any media type, respectively. While java.io.File and byte[] can als be used for input and output of any media type. All these options are rather a low-level API option.

  • String and char[] can be used for input and output of text-based values.

  • MultivaluedMap<String, String> can be used to receive HTTP requests form data encoded as the application/x-www-form-urlencoded media type. It’s also possible to use it to return data through the HTTP response.

There’s a section of the book in this chapter that explains how to implement custom marshalling and unmarshalling.

Chapter 7: Server Responses and Exception Handling

Standard HTTP success response code numbers range from 200 to 399.

Standard HTTP error response code numbers range from 400 to 599.

ResponseBuilder can be used to build custom Response objects.

Errors can be reported to a client either by creating and returning the appropriate Response object or by throwing an exception.

Own implementations of jakarta.ws.rs.ext.ExceptionMapper could be used to map a thrown application exception to a Response object.

Chapter 8: JAX-RS Client API

Example of a request to fetch a resource by using the jakarta.ws.rs.client API (wrapped inside a Java main method for demonstration purposes only).

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
import javax.net.ssl.SSLContext;

public class Main {
    public static void main(String[] args) { // in a real application this code would go inside a service
        SSLContext sslContext = sslContext();

        Client client = ClientBuilder.newBuilder()
                                     .property("connection.timeout", 100)
                                     .sslContext(sslContext)
                                     .register(JacksonJsonProvider.class)
                                     .build();

        WebTarget target = client.target("https://example.com/resources/{type}")
                                 .resolveTemplate("type", "active")
                                 .queryParam("verbose", true);

        Response response = target.post(Entity.xml(new Resource("R1", "A1")));
        response.close();

        boolean redirected = false;
        Resource resource = null;
        do {
            try {
                resource = target.queryParam("name", "R1")
                                 .accept("application/json")
                                 .get(Resource.class);
            } catch (NotAcceptableException notAcceptable) {
                // code omitted for brevity: do something with notAcceptable
            } catch (NotFoundException notFound) {
                // code omitted for brevity: do something with notFound
            } catch (RedirectionException redirect) {
                if (redirected) { // allow only one redirect
                    break;
                }
                redirected = true;
                target = client.target(redirect.getLocation());
            }
        } while (resource == null);

        client.close();
        if (resource == null) {
            // code omitted for brevity: throw exception
        }
    }

    private static SSLContext sslContext() {
        // code omitted for brevity
    }
}

class Resource {
    // code omitted for brevity
    Resource(String name, String alias) {
        // code omitted for brevity
    }
}

Chapter 9: HTTP Content Negotiation

Clients can request a specific media type they would like returned when querying a server for information. They can set an Accept request header that is a comma-delimited list of preferred formats. For example:

GET https://example.com/resource
Accept: application/xml, application/json

More specific media types take precedence over less specific ones.

Clients can also be more specific on their preferences by using the q MIME type property. This property is a numeric value between 0.0 (least preferred) and 1.0 (most preferred), being 1.0 the default value.

For example:

GET https://example.com/stuff
Accept: text/*;q=0.9, */*;q=0.1, audio/mpeg, application/xml;q=0.5

The Accept-Language header can be set to specify which human language they would like to receive. For example:

GET https://example.com/stuff
Accept-Language: en-us, es, fr

The Accept-Encoding header can be set to specify which encodings client support. For example:

GET https://example.com/stuff
Accept-Encoding: gzip, deflate

When a client or server encodes a message body, the Content-Encoding header must be set to inform the receiver which encoding was used.

By designing our own application media types we confine the complexity of different and new data types to our custom data formats. A common way to address this is to define custom media types using the vnd+<custom_name> where custom name is any string we’d like to define.

For example, a specific XML format for company Acme could be defined like this: application/vnd.acm.resource+xml, where vnd stands for "vendor", acm stands for "Acme", resource is the name of the resource and xml is the format used to return the data.

After a base media type name is created, we can append versioning information to it so that older clients can still ask for older versions of the format: application/vnd.acm.resource+xml;version=1.0.

Chapter 10: HATEOAS

HATEOAS stands for "Hypermedia As The Engine Of Application State" and aims to make easier to integrate clients and services by making their interactions decoupled and easily evolvable after application changes.

JAX-RS doesn’t have many facilities to help with HATEOAS. However, it has helper classes that we can use to build the URIs that we can link to in our data formats. For example, UriBuilder. It could be used like this:

// ...
UriBuilder builder = UriBuilder.fromPath("/my-resources/{id}")  // (1)
                               .scheme("https")
                               .host("{hostname}")
                               .queryParam("param={param}");

UriBuilder clone = builder.clone();                             // (2)

URI uri = clone.build("lealceldeiro.com", "333", "value");      // (3)
// ...
1 Define a URI pattern that looks like this: https://{hostname}/my-resources/{id}?param={param}
2 To re-use the builder, we get a clone first because the build method will replace the template parameters internally
3 Create a URI that looks like this: https://lealceldeiro.com/my-resources/333?param=value

We could also define a map that contains the template values. Like this:

Map<String, Object> map = Map.of("hostname", "lealceldeiro.com",
                                 "id", "333",
                                 "param", "value");
URI uri = clone.buildFromMap(map);  // re-use previously created `clone`

The Link and Link.Builder classes, from JAX-RS 2.0, allows building Link headers and embedding links in the XML documents. Example:

@Path("/my-resource")
class MyResource {
    @GET
    Response get() {
        Link link = Link.fromUri("a/b/c").build();
        return Response.noContent().links(link).build();
    }
}

Chapter 11: Scaling JAX-RS Applications

The JAX-RS specification provides the class jakarta.ws.rs.core.CacheControl, useful to represent the Cache-Control header.

JAX-RS has a simple class called jakarta.ws.rs.core.EntityTag, that represents the ETag header.

Simultaneous requests to update a resources through a POST or PUT HTTP method can cause concurrency issues if the proper measures aren’t taken into account.

One way to handle this successfully is to have the clients first pull the current representation of the resource that’s going to be modified. The pulled information from the server should include an ETag and/or a Last-Modified header(s). Then, when the update request is issued to the server, it should include an If-Match (for the received ETag) and/or an `If-Unmodified-Since (for the received Last-Modified) headers. Example:

Get resource’s current representation:

HTTP/1.1 200 OK
Content-Type: application/xml
Cache-Control: max-age=1000
ETag: "3141271342554322343200"
Last-Modified: Sat, 17 Feb 2024 09:56 EST
<resource id="333">...</resource>

Update resource:

PUT /resources/123 HTTP/1.1
If-Match: "3141271342554322343200"
If-Unmodified-Since: Sat, 17 Feb 2024 09:56 EST
Content-Type: application/xml
<resources id="333">...</resource>

When the server receives this request, it checks to see if the current ETag of the resource matches the value of the If-Match header and also to see if the timestamp on the resource matches the If-Unmodified-Since header. If these conditions aren’t met, the server will return an error response code of 412 Precondition Failed.

Chapter 12: Filters and Interceptors

On the server side there are two different types of filters: request filters and response filters. Request filters execute before a JAX-RS method is invoked. Response filters are executed after the JAX-RS method is finished.

Request filters are implementations of the ContainerRequestFilter interface. Example:

import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerRequestContext;

@Provider
@PreMatching
public class HttpMethodOverride implements ContainerRequestFilter {
    public void filter(ContainerRequestContext ctx) throws IOException {
        String methodOverride = ctx.getHeaderString("X-Http-Method-Override");
        if (methodOverride != null) {
            ctx.setMethod(methodOverride);
        }
    }
}

On the server side, filters and interceptors are deployed the same way any other @Provider is deployed. We either annotate it with @Provider and let it be scanned and automatically registered, or we add the filter or interceptor to the Application class’s classes or singleton list.

On the client side, we register filters and interceptors the same way we’d register any other provider. There are a few components in the Client API that implement the Configurable interface. This interface has a register() method that allows us to pass in the filter or interceptor class or singleton instance. ClientBuilder, Client, and WebTarget all implement the Configurable interface.

Interestingly, we can have different filters and interceptors per WebTarget.

For example, we may have different security requirements for different HTTP resources. For one WebTarget instance, we might register a Basic Auth filter. For another, we might register a token filter.

In JAX-RS, filters and interceptors are assigned a numeric priority either through the @Priority annotation or via a programmatic interface defined by Configurable. The JAX-RS runtime sorts filters and interceptors based on this numeric priority, where smaller numbers are first in the chain.

Chapter 13: Asynchronous JAX-RS

The asynchronous request and response processing feature was introduced in JAX-RS 2.0.

The client asynchronous API allows us to spin off several HTTP requests in the background and then either poll for a response, or register a callback to be invoked when the HTTP response is available.

Example that "waits" for a response:

// imports omitted for brevity

class Service {
    public void fetchResources() {
        Client client = ClientBuilder.newClient();

        Future<Response> fruitsFuture = client.target("https://fruitsite.com/fruits/333").request().async().get();
        // block until complete
        Response fruitsRes = fruitsFuture.get();
        try {
            Fruit fruit = fruitsRes.readEntity(Fruit.class);
        } catch (Throwable e) {
            // exception handling omitted for brevity
        } finally{
            fruitsRes.close();
        }

        Future<Color> colorsFuture = null;
        try {
            colorsFuture = client.target("https://colorsite.com/colors/111").request().async().get(Color.class);
        } catch (Throwable e) {
            // exception handling omitted for brevity
        }

        if (colorsFuture != null) {
            // wait max 5 seconds
            try {
                Color color = colorsFuture.get(5, TimeUnit.SECONDS);
            } catch (TimeoutException timeout) {
                // TimeoutException handling omitted for brevity
            } catch (InterruptedException interruptedException) {
                // InterruptedException handling omitted for brevity
            } catch (ExecutionException executionException) {
                // ExecutionException handling omitted for brevity
            }
        }
    }
}

Example that registers a callback to be executed when a response is available:

// imports omitted for brevity

class FruitCallback implements InvocationCallback<Response> {
    @Override
    public void completed(Response response) {
        if (response.getStatus() == 200) {
            Fruit fruit = response.readEntity(Fruit.class);
        } else {
            log.error("Request error: " + response.getStatus());
        }
    }

    @Override
    public void failed(Throwable throwable) {
        log.error(throwable);
    }
}

class ColorCallback implements InvocationCallback<Color> {
    @Override
    public void completed(Color color) {
        // process order, code omitted for brevity
    }

    @Override
    public void failed(Throwable throwable) {
        if (throwable instanceof WebApplicationException wae) {
            log.error("Failed with status: " + wae.getResponse().getStatus());
        } else if (throwable instanceof ResponseProcessingException rpe) {
            log.error("Failed with status: " + rpe.getResponse().getStatus());
        } else {
            // code omitted for brevity: handle other eceptions
        }
    }
}

class Service {
    public void fetchResources() {
        Client client = ClientBuilder.newClient();
        Future<Response> fruitsFuture = client.target("https://fruitsite.com/fruits/333")
                                              .request()
                                              .async()
                                              .get(new FruitCallback());
        Future<Color> colorsFuture = client.target("https://colorsite.com/colors/111")
                                           .request()
                                           .async()
                                           .get(new ColorCallback());
    }
}

Given, there are two options to execute/handle asynchronoys requests it might be advisable to use `Future`s when we need to join several requests/responses. This could be for example, execute all sync requests and do another task after all of them have completed.

The callback approach could be used when each async request is an independent unit of work and no other task depends on the overall execution of the group of independent async tasks. This means there’s no need to do any coordination work among the async tasks.

To use server-side async response processing, we interact with the AsyncResponse interface. Example:

import jakarta.ws.rs.container.AsyncResponse;
import jakarta.ws.rs.container.Suspended;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// other imports omitted for brevity

@Path("/colors")
public class ColorResource {
    private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();

    @POST
    @Consumes("application/json")
    public void submit(final Color color, final @Suspended AsyncResponse response) {
        EXECUTOR.submit(() -> {
            Color color = colorProcessor.process(color);
            response.resume(color);
            // alternatively, we can pass resume() a Response object to send the client a more specific response
        });
    }
}

When we inject an instance of AsyncResponse using the @Suspended annotation, the HTTP request becomes suspended from the current thread of execution.

Use Cases for AsyncResponse

  1. Server-side push

    • i.e.: to send events back to the client, like stock quotes

  2. Publish and subscribe

    • i.e.: a chat service

  3. Priority scheduling

    • i.e.: to schedule CPU-intensive tasks in a separate pool, to make sure other tasks don’t starve

Chapter 14: Deployment and Integration

The jakarta.ws.rs.code.Application class is the only portable way of telling JAX-RS which web services (@Path annotated classes) as well as which filters, interceptors, MessageBodyReaders, MessageBodyWriters, and ContextResolvers (providers) you want deployed.

Java EE stands for Java Enterprise Edition. It’s the umbrella specification of JAX-RS and defines a complete enterprise platform that includes services like a servlet container, EJB, transaction manager (JTA), messaging (JMS), connection pooling (JCA), database persistence (JPA), web framework (JSF), and a multitude of other services.

In 2022, Java EE was superseded by Jakarta EE.

Chapter 15: Securing JAX-RS

The jakarta.ws.rs.core.SecurityContext interface has a method for determining the identity of the user making the secured HTTP invocation.

DomainKeys Identified Mail (DKIM) is a digital signature protocol that was designed for email. It’s simply a request or response header that contains a digital signature of one or more headers of the message and the content.

Example:

DKIM-Signature: v=1;
                a=rsa-sha256;
                d=example.com;
                s=burke;
                c=simple/simple;
                h=Content-Type;
                x=0023423111111;
                bh=2342322111;
                b=M232234=

Where,

  • v: Protocol version.

  • a: Algorithm used to hash and sign the message.

  • b: Domain of the signer.

  • s: Selector of the domain.

  • c: Canonical algorithm.

  • h: Semicolon-delimited list of headers that are included in the signature calculation.

  • x: When the signature expires.

  • t: Timestamp of signature.

  • bh: Base 64–encoded hash of the message body

  • b: Base 64–encoded signature

Chapter 16: Alternative Java Clients

  • java.net.URL

  • Apache HttpClient

  • RESTEasy Client Proxies