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
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.
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
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.
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
}
In general packages |
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.
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:
The primary key of the sort is the number of literal characters in the full URI matching pattern. The sort is in descending order.
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.
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.
|
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 |
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:
It is a primitive type.
The int
, short
, float
, double
, byte
, char
, and boolean
types all fit into this category.
It is a Java class that has a constructor with a single String
parameter.
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.
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
.
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.
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.
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
}
}
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
.
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();
}
}
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
.
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 For example, we may have different security requirements for different HTTP resources.
For one |
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.
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.
Server-side push
i.e.: to send events back to the client, like stock quotes
Publish and subscribe
i.e.: a chat service
Priority scheduling
i.e.: to schedule CPU-intensive tasks in a separate pool, to make sure other tasks don’t starve
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.
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
java.net.URL
Apache HttpClient
RESTEasy Client Proxies