This guide explains how to write REST Services with RESTEasy Reactive in Quarkus.

What is RESTEasy Reactive?

RESTEasy Reactive is a new JAX-RS implementation written from the ground up to work on our common Vert.x layer and is thus fully reactive, while also being very tightly integrated with Quarkus and consequently moving a lot of work to build time.

You should be able to use it in place of any JAX-RS implementation, but on top of that it has great performance for both blocking and non-blocking endpoints, and a lot of new features on top of what JAX-RS provides.

Writing endpoints

Getting started

Add the following import to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-reactive")

You can now write your first endpoint in the org.acme.rest.Endpoint class:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("")
public class Endpoint {

    @GET
    public String hello() {
        return "Hello, World!";
    }
}

Terminology

REST

REpresentational State Transfer

Endpoint

Java method which is called to serve a REST call

URL / URI (Uniform Resource Locator / Identifier)

Used to identify the location of REST resources (specification)

Resource

Represents your domain object. This is what your API serves and modifies. Also called an entity in JAX-RS.

Representation

How your resource is represented on the wire, can vary depending on content types

Content type

Designates a particular representation (also called a media type), for example text/plain or application/json

HTTP

Underlying wire protocol for routing REST calls (see HTTP specifications)

HTTP request

the request part of the HTTP call, consisting of an HTTP method, a target URI, headers and an optional message body

HTTP response

the response part of the HTTP call, consisting of an HTTP response status, headers and an optional message body

Declaring endpoints: URI mapping

Any class annotated with a @Path annotation can have its methods exposed as REST endpoints, provided they have an HTTP method annotation (see below).

That @Path annotation defines the URI prefix under which those methods will be exposed. It can be empty, or contain a prefix such as rest or rest/V1.

Each exposed endpoint method can in turn have another @Path annotation which adds to its containing class annotation. For example, this defines a rest/hello endpoint:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("rest")
public class Endpoint {

    @Path("hello")
    @GET
    public String hello() {
        return "Hello, World!";
    }
}

See URI parameters for more information about URI mapping.

You can set the root path for all rest endpoints using the @ApplicationPath annotation, as shown below.

package org.acme.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public static class MyApplication extends Application {

}

This will cause all rest endpoints to be resolve relative to /api, so the endpoint above with @Path("rest") would be accessible at /api/rest/. You can also set the `quarkus.rest.path build time property to set the root path if you don’t want to use an annotation.

Declaring endpoints: HTTP methods

Each endpoint method must be annotated with one of the following annotations, which defines which HTTP method will be mapped to the method:

Table 1. Table HTTP method annotations
Annotation Usage

@GET

Obtain a resource representation, should not modify state, idempotent (HTTP docs)

@HEAD

Obtain metadata about a resource, similar to GET with no body (HTTP docs)

@POST

Create a resource and obtain a link to it (HTTP docs)

@PUT

Replace a resource or create one, should be idempotent (HTTP docs)

@DELETE

Delete an existing resource, idempotent (HTTP docs)

@OPTIONS

Obtain information about a resource, idempotent (HTTP docs)

@PATCH

Update a resource, or create one, not idempotent (HTTP docs)

You can also declare other HTTP methods by declaring them as an annotation with the @HttpMethod annotation:

package org.acme.rest;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;

@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("FROMAGE")
@interface FROMAGE {
}

@Path("")
public class Endpoint {

    @FROMAGE
    public String hello() {
        return "Hello, Cheese World!";
    }
}

Declaring endpoints: representation / content types

Each endpoint method may consume or produce specific resource representations, which are indicated by the HTTP Content-Type header, which in turn contains MIME (Media Type) values, such as the following:

  • text/plain which is the default for any endpoint returning a String.

  • text/html for HTML (such as with Qute templating)

  • application/json for a JSON REST endpoint

  • text/* which is a sub-type wildcard for any text media type

  • */* which is a wildcard for any media type

You may annotate your endpoint class with the @Produces or @Consumes annotations, which allow you to specify one or more media types that your endpoint may accept as HTTP request body or produce as HTTP response body. Those class annotations apply to each method.

Any method may also be annotated with the @Produces or @Consumes annotations, in which case they override any eventual class annotation.

The MediaType class has many constants you can use to point to specific pre-defined media types.

See Negotiation for more information.

Accessing request parameters

don’t forget to configure your compiler to generate parameter name information with -parameters (javac) or <parameters> or <maven.compiler.parameters> (Maven).

The following HTTP request elements may be obtained by your endpoint method:

Table 2. Table HTTP request parameter annotations
HTTP element Annotation Usage

Path parameter

@RestPath (or nothing)

URI template parameter (simplified version of the URI Template specification), see URI parameters for more information.

Query parameter

@RestQuery

The value of an URI query parameter

Header

@RestHeader

The value of an HTTP header

Cookie

@RestCookie

The value of an HTTP cookie

Form parameter

@RestForm

The value of an HTTP URL-encoded FORM

Matrix parameter

@RestMatrix

The value of an URI path segment parameter

For each of those annotations, you may specify the name of the element they refer to, otherwise they will use the name of the annotated method parameter.

If a client made the following HTTP call:

POST /cheeses;variant=goat/tomme?age=matured HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: level=hardcore
X-Cheese-Secret-Handshake: fist-bump

smell=strong

Then you could obtain all the various parameters with this endpoint method:

package org.acme.rest;

import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.jboss.resteasy.reactive.RestCookie;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestHeader;
import org.jboss.resteasy.reactive.RestMatrix;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestQuery;

@Path("/cheeses/{type}")
public class Endpoint {

    @POST
    public String allParams(@RestPath String type,
                            @RestMatrix String variant,
                            @RestQuery String age,
                            @RestCookie String level,
                            @RestHeader("X-Cheese-Secret-Handshake")
                            String secretHandshake,
                            @RestForm String smell) {
        return type + "/" + variant + "/" + age + "/" + level + "/" + secretHandshake + "/" + smell;
    }
}
the @RestPath annotation is optional: any parameter whose name matches an existing URI template variable will be automatically assumed to have @RestPath.

You can also use any of the JAX-RS annotations @PathParam, @QueryParam, @HeaderParam, @CookieParam, @FormParam or @MatrixParam for this, but they require you to specify the parameter name.

See Parameter mapping for more advanced use-cases.

Declaring URI parameters

You can declare URI parameters and use regular expressions in your path, so for instance the following endpoint will serve requests for /hello/stef/23 and /hello but not /hello/stef/0x23:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("hello")
public class Endpoint {

    @Path("{name}/{age:\\d+}")
    @GET
    public String personalisedHello(String name, int age) {
        return "Hello " + name + " is your age really " + age + "?";
    }

    @GET
    public String genericHello() {
        return "Hello stranger";
    }
}

Accessing the request body

Any method parameter with no annotation will receive the method body.[1], after it has been mapped from its HTTP representation to the Java type of the parameter.

The following parameter types will be supported out of the box:

Table 3. Table Request body parameter type
Type Usage

File

The entire request body in a temporary file

byte[]

The entire request body, not decoded

char[]

The entire request body, decoded

String

The entire request body, decoded

InputStream

The request body in a blocking stream

Reader

The request body in a blocking stream

All Java primitives and their wrapper classes

Java primitive types

BigDecimal, BigInteger

Large integers and decimals.

JsonArray, JsonObject, JsonStructure, JsonValue

JSON value types

Buffer

Vert.x Buffer

any other type

Will be mapped from JSON to that type

You can add support for more body parameter types.

Handling Multipart Form data

To handle HTTP requests that have multipart/form-data as their content type, RESTEasy Reactive introduces the @MultipartForm annotation. Let us look at an example of its use.

Assuming an HTTP request containing a file upload and a form value containing a string description need to be handled, we could write a POJO that will hold this information like so:

import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;

public class FormData {

    @RestForm
    @PartType(MediaType.TEXT_PLAIN)
    public String description;

    @RestForm("image")
    public FileUpload file;
}

The name field will contain the data contained in the part of HTTP request called description (because @RestForm does not define a value, the field name is used), while the file field will contain data about the uploaded file in the image part of HTTP request.

FileUpload provides access to various metadata of the uploaded file. If however all you need is a handle to the uploaded file, java.nio.file.Path or java.io.File could be used.
When access to all uploaded files without specifying the form names is needed, RESTEasy Reactive allows the use of @RestForm List<FileUpload>, where it is important to not set a name for the @RestForm annotation.
@PartType is used to aid in deserialization of the corresponding part of the request into the desired Java type. It is very useful when for example the corresponding body part is JSON and needs to be converted to a POJO.

This POJO could be used in a Resource method like so:

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.MultipartForm;

@Path("multipart")
public class Endpoint {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Path("form")
    public String form(@MultipartForm FormData formData) {
        // return something
    }
}

The use of @MultipartForm as method parameter makes RESTEasy Reactive handle the request as a multipart form request.

The use of @MultipartForm is actually unnecessary as RESTEasy Reactive can infer this information from the use of @Consumes(MediaType.MULTIPART_FORM_DATA)
When handling file uploads, it is very important to move the file to permanent storage (like a database, a dedicated file system or a cloud storage) in your code that handles the POJO. Otherwise, the file will no longer be accessible when the request terminates. Moreoever if quarkus.http.body.delete-uploaded-files-on-end is set to true, Quarkus will delete the uploaded file when the HTTP response is sent. If the setting is disabled, the file will reside on the file system of the server (in the directory defined by the quarkus.http.body.uploads-directory configuration option), but as the uploaded files are saved with a UUID file name and no additional metadata is saved, these files are essentially a random dump of files.

Similarly, RESTEasy Reactive can produce Multipart Form data to allow users download files from the server. For example, we could write a POJO that will hold the information we want to expose as:

import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;

public class DownloadFormData {

    @RestForm
    String name;

    @RestForm
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    File file;
}

And then expose this POJO via a Resource like so:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("multipart")
public class Endpoint {

    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    @Path("file")
    public DownloadFormData getFile() {
        // return something
    }
}
For the time being, returning Multipart data is limited to be blocking endpoints.

Returning a response body

In order to return an HTTP response, simply return the resource you want from your method. The method return type and its optional content type will be used to decide how to serialise it to the HTTP response (see Negotiation for more advanced information).

You can return any of the pre-defined types that you can read from the HTTP response, and any other type will be mapped from that type to JSON.

In addition, the following return types are also supported:

Table 4. Table Additional response body parameter type
Type Usage

Path

The contents of the file specified by the given path

PathPart

The partial contents of the file specified by the given path

FilePart

The partial contents of a file

AsyncFile

Vert.x AsyncFile, which can be in full, or partial

Alternately, you can also return a reactive type such as Uni, Multi or CompletionStage that resolve to one of the mentioned return types.

Setting other response properties

Manually setting the response

If you need to set more properties on the HTTP response than just the body, such as the status code or headers, you can make your method return org.jboss.resteasy.reactive.RestResponse from a resource method. An example of this could look like:

package org.acme.rest;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;

import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder;

@Path("")
public class Endpoint {

    @GET
    public RestResponse<String> hello() {
        // HTTP OK status with text/plain content type
        return ResponseBuilder.ok("Hello, World!", MediaType.TEXT_PLAIN_TYPE)
         // set a response header
         .header("X-FroMage", "Camembert")
         // set the Expires response header to two days from now
         .expires(Date.from(Instant.now().plus(Duration.ofDays(2))))
         // send a new cookie
         .cookie(new NewCookie("Flavour", "praliné"))
         // end of builder API
         .build();
    }
}
You can also use the JAX-RS type Response but it is not strongly typed to your entity.

Using annotations

Alternatively, if you only need to set the status code and / or HTTP headers with static values, you can use @org.jboss.resteasy.reactive.ResponseStatus and /or ResponseHeader respectively. An example of this could look like:

package org.acme.rest;

import org.jboss.resteasy.reactive.Header;
import org.jboss.resteasy.reactive.ResponseHeaders;
import org.jboss.resteasy.reactive.ResponseStatus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("")
public class Endpoint {

    @ResponseStatus(201)
    @ResponseHeader(name = "X-FroMage", value = "Camembert")
    @GET
    public String hello() {
        return "Hello, World!";
    }
}

Async/reactive support

If your endpoint method needs to accomplish an asynchronous or reactive task before being able to answer, you can declare your method to return the Uni type (from Mutiny), in which case the current HTTP request will be automatically suspended after your method, until the returned Uni instance resolves to a value, which will be mapped to a response exactly according to the previously described rules:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("escoffier")
public class Endpoint {

    @GET
    public Uni<Book> culinaryGuide() {
        return Book.findByIsbn("978-2081229297");
    }
}

This allows you to not block the event-loop thread while the book is being fetched from the database, and allows Quarkus to serve more requests until your book is ready to be sent to the client and terminate this request. Check out our Execution Model documentation for more information.

The CompletionStage return type is also supported.

Streaming support

If you want to stream your response element by element, you can make your endpoint method return a Multi type (from Mutiny). This is especially useful for streaming text or binary data.

This example, using Reactive Messaging HTTP shows how to stream text data:

package org.acme.rest;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.eclipse.microprofile.reactive.messaging.Channel;

import io.smallrye.mutiny.Multi;

@Path("logs")
public class Endpoint {

    @Inject
    @Channel("log-out")
    Multi<String> logs;

    @GET
    public Multi<String> streamLogs() {
        return logs;
    }
}
Response filters are not invoked on streamed responses, because they would give a false impression that you can set headers or HTTP status codes, which is not true after the initial response.

Server-Sent Event (SSE) support

If you want to stream JSON objects in your response, you can use Server-Sent Events by just annotating your endpoint method with @Produces(MediaType.SERVER_SENT_EVENTS) and specifying that each element should be serialised to JSON with @RestStreamElementType(MediaType.APPLICATION_JSON).

package org.acme.rest;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.RestStreamElementType;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;

import io.smallrye.reactive.messaging.annotations.Channel;

@Path("escoffier")
public class Endpoint {

    // Inject our Book channel
    @Inject
    @Channel("book-out")
    Multi<Book> books;

    @GET
    // Send the stream over SSE
    @Produces(MediaType.SERVER_SENT_EVENTS)
    // Each element will be sent as JSON
    @RestStreamElementType(MediaType.APPLICATION_JSON)
    public Multi<Book> stream() {
        return books;
    }
}

Controlling HTTP Caching features

RESTEasy Reactive provides the @Cache and @NoCache annotations to facilitate handling HTTP caching semantics, i.e. setting the 'Cache-Control' HTTP header.

These annotations can be placed either on a Resource Method or a Resource Class (in which case it applies to all Resource Methods of the class that do not contain the same annotation) and allow users to return domain objects and not have to deal with building up the Cache-Control HTTP header explicitly.

While @Cache builds a complex Cache-Control header, @NoCache is a simplified notation to say that you don’t want anything cached; i.e. Cache-Control: nocache.

More information on the Cache-Control header and be found in RFC 7234

Accessing context objects

There are a number of contextual objects that the framework will give you, if your endpoint method takes parameters of the following type:

Table 5. Table Context object
Type Usage

HttpHeaders

All the request headers

ResourceInfo

Information about the current endpoint method and class (requires reflection)

SecurityContext

Access to the current user and roles

SimpleResourceInfo

Information about the current endpoint method and class (no reflection required)

UriInfo

Provides information about the current endpoint and application URI

Application

Advanced: Current JAX-RS application class

Configuration

Advanced: Configuration about the deployed JAX-RS application

Providers

Advanced: Runtime access to JAX-RS providers

Request

Advanced: Access to the current HTTP method and Preconditions

ResourceContext

Advanced: access to instances of endpoints

ServerRequestContext

Advanced: RESTEasy Reactive access to the current request/response

Sse

Advanced: Complex SSE use-cases

HttpServerRequest

Advanced: Vert.x HTTP Request

HttpServerResponse

Advanced: Vert.x HTTP Response

For example, here is how you can return the name of the currently logged-in user:

package org.acme.rest;

import java.security.Principal;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    @GET
    public String userName(SecurityContext security) {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

You can also inject those context objects using @Inject on fields of the same type:

package org.acme.rest;

import java.security.Principal;

import javax.inject.Inject;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    @Inject
    SecurityContext security;

    @GET
    public String userName() {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

Or even on your endpoint constructor:

package org.acme.rest;

import java.security.Principal;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    SecurityContext security;

    Endpoint(SecurityContext security) {
        this.security = security;
    }

    @GET
    public String userName() {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

JSON serialisation

Instead of importing io.quarkus:quarkus-resteasy-reactive, you can import either of the following modules to get support for JSON:

Table 6. Table Context object
GAV Usage

io.quarkus:quarkus-resteasy-reactive-jackson

Jackson support

io.quarkus:quarkus-resteasy-reactive-jsonb

JSON-B support

In both cases, importing those modules will allow HTTP message bodies to be read from JSON and serialised to JSON, for all the types not already registered with a more specific serialisation.

Advanced Jackson-specific features

When using the quarkus-resteasy-reactive-jackson extension there are some advanced features that RESTEasy Reactive supports.

Secure serialization

When used with Jackson to perform JSON serialization, RESTEasy Reactive provides the ability to limit the set of fields that are serialized based on the roles of the current user. This is achieved by simply annotating the fields (or getters) of the POJO being returned with @io.quarkus.resteasy.reactive.jackson.SecureField.

A simple example could be the following:

Assume we have a POJO named Person which looks like so:

package org.acme.rest;

import io.quarkus.resteasy.reactive.jackson.SecureField;

public class Person {

    @SecureField(rolesAllowed = "admin")
    private final Long id;
    private final String first;
    private final String last;

    public Person(Long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }

    public Long getId() {
        return id;
    }

    public String getFirst() {
        return first;
    }

    public String getLast() {
        return last;
    }
}

A very simple JAX-RS Resource that uses Person could be:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("person")
public class Person {

    @Path("{id}")
    @GET
    public Person getPerson(Long id) {
        return new Person(id, "foo", "bar");
    }
}

Assuming security has been set up for the application (see our guide for more details), when a user with the admin role performs an HTTP GET on /person/1 they will receive:

{
  "id": 1,
  "first": "foo",
  "last": "bar"
}

as the response.

Any user however that does not have the admin role will receive:

{
  "first": "foo",
  "last": "bar"
}
No additional configuration needs to be applied for this secure serialization to take place. However, users can use the @io.quarkus.resteasy.reactive.jackson.EnableSecureSerialization and @io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization annotation to opt-in or out for specific JAX-RS Resource classes or methods.
@JsonView support

JAX-RS methods can be annotated with @JsonView in order to customize the serialization of the returned POJO, on a per method-basis. This is best explained with an example.

A typical use of @JsonView is to hide certain fields on certain methods. In that vein, let’s define two views:

public class Views {

    public static class Public {
    }

    public static class Private extends Public {
    }
}

Let’s assume we have the User POJO on which we want to hide some field during serialization. A simple example of this is:

public class User {

    @JsonView(Views.Private.class)
    public int id;

    @JsonView(Views.Public.class)
    public String name;
}

Depending on the JAX-RS method that returns this user, we might want to exclude the id field from serialization - for example you might want an insecure method to not expose this field. The way we can achieve that in RESTEasy Reactive is shown in the following example:

@JsonView(Views.Public.class)
@GET
@Path("/public")
public User userPublic() {
    return testUser();
}

@JsonView(Views.Private.class)
@GET
@Path("/private")
public User userPrivate() {
    return testUser();
}

When the result the userPublic method is serialized, the id field will not be contained in the response as the Public view does not include it. The result of userPrivate however will include the id as expected when serialized.

Completely customized per method serialization

There are times when you need to completely customize the serialization of a POJO on a per JAX-RS method basis. For such use cases, the @io.quarkus.resteasy.reactive.jackson.CustomSerialization annotation is a great tool, as it allows you to configure a per-method com.fasterxml.jackson.databind.ObjectWriter which can be configured at will.

Here is an example use case:

@CustomSerialization(UnquotedFields.class)
@GET
@Path("/invalid-use-of-custom-serializer")
public User invalidUseOfCustomSerializer() {
    return testUser();
}

where UnquotedFields is a BiFunction defined as so:

public static class UnquotedFields implements BiFunction<ObjectMapper, Type, ObjectWriter> {

    @Override
    public ObjectWriter apply(ObjectMapper objectMapper, Type type) {
        return objectMapper.writer().without(JsonWriteFeature.QUOTE_FIELD_NAMES);
    }
}

Essentially what this class does is force Jackson to not include quotes in the field names.

It is important to note that this customization is only performed for the serialization of the JAX-RS methods that use @CustomSerialization(UnquotedFields.class).

XML serialisation

To enable XML support, add the quarkus-resteasy-reactive-jaxb extension to your project.

Table 7. Table Context object
GAV Usage

io.quarkus:quarkus-resteasy-reactive-jaxb

XML support

Importing this module will allow HTTP message bodies to be read from XML and serialised to XML, for all the types not already registered with a more specific serialisation.

More advanced usage

Here are some more advanced topics that you may not need to know about initially, but could prove useful for more complex use cases.

Execution model, blocking, non-blocking

RESTEasy Reactive is implemented using two main thread types:

  • Event-loop threads: which are responsible, among other things, for reading bytes from the HTTP request and writing bytes back to the HTTP response

  • Worker threads: they are pooled and can be used to offload long-running operations

The event-loop threads (also called IO threads) are responsible for actually performing all the IO operations in an asynchronous way, and to trigger any listener interested in the completion of those IO operations.

By default, the thread RESTEasy Reactive will run endpoint methods on depends on the signature of the method. If a method returns one of the following types then it is considered non-blocking, and will be run on the IO thread by default:

  • io.smallrye.mutiny.Uni

  • io.smallrye.mutiny.Multi

  • java.util.concurrent.CompletionStage

  • org.reactivestreams.Publisher

  • Kotlin suspended methods

This 'best guess' approach means that the majority of operations will run on the correct thread by default. If you are writing reactive code then your method will generally return one of these types, and will be executed on the IO thread. If you are writing blocking code your methods will generally return the result directly, and these will be run on a worker thread.

You can override this behaviour using the @Blocking and @NonBlocking annotations. This can be applied at the method, class or javax.ws.rs.core.Application level.

The example below will override the default behaviour and always run on a worker thread, even though it returns a Uni.

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.common.annotation.Blocking;

@Path("yawn")
public class Endpoint {

    @Blocking
    @GET
    public Uni<String> blockingHello() throws InterruptedException {
        // do a blocking operation
        Thread.sleep(1000);
        return Uni.createFrom().item("Yaaaawwwwnnnnnn…");
    }
}

Most of the time, there are ways to achieve the same blocking operations in an asynchronous/reactive way, using Mutiny, Hibernate Reactive or any of the Quarkus Reactive extensions for example:

package org.acme.rest;

import java.time.Duration;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("yawn")
public class Endpoint {

    @GET
    public Uni<String> blockingHello() throws InterruptedException {
        return Uni.createFrom().item("Yaaaawwwwnnnnnn…")
                // do a non-blocking sleep
                .onItem().delayIt().by(Duration.ofSeconds(2));
    }
}

If a method or class is annotated with javax.transaction.Transactional then it will also be treated as a blocking method. This is because JTA is a blocking technology, and is generally used with other blocking technology such as Hibernate and JDBC. An explicit @Blocking or @NonBlocking on the class will override this behaviour.

Overriding the default behaviour

If you want to override the default behaviour you can annotate a javax.ws.rs.core.Application subclass in your application with @Blocking or @NonBlocking, and this will set the default for every method that does not have an explicit annotation.

Behaviour can still be overridden on a class or method level by annotating them directly, however all endpoints without an annotation will now follow the default, no matter their method signature.

Exception mapping

If your application needs to return non-nominal HTTP codes in error cases, the best is to throw exceptions that will result in the proper HTTP response being sent by the framework using WebApplicationException or any of its subtypes:

package org.acme.rest;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;

@Path("fromages/{fromage}")
public class Endpoint {

    @GET
    public String findFromage(String fromage) {
        if(fromage == null)
            // send a 400
            throw new BadRequestException();
        if(!fromage.equals("camembert"))
            // send a 404
            throw new NotFoundException("Unknown cheese: " + fromage);
        return "Camembert is a very nice cheese";
    }
}

If your endpoint method is delegating calls to another service layer which does not know of JAX-RS, you need a way to turn service exceptions to an HTTP response, and you can do that using the @ServerExceptionMapper annotation on a method, with one parameter of the exception type you want to handle, and turning that exception into a RestResponse (or a Uni<RestResponse<?>>):

package org.acme.rest;

import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.RestResponse;

class UnknownCheeseException extends RuntimeException {
    public final String name;

    public UnknownCheeseException(String name) {
        this.name = name;
    }
}

@ApplicationScoped
class CheeseService {
    private static final Map<String, String> cheeses =
            Map.of("camembert", "Camembert is a very nice cheese",
                   "gouda", "Gouda is acceptable too, especially with cumin");

    public String findCheese(String name) {
        String ret = cheeses.get(name);
        if(ret != null)
            return ret;
        throw new UnknownCheeseException(name);
    }
}

@Path("fromages/{fromage}")
public class Endpoint {

    @Inject
    CheeseService cheeses;

    @ServerExceptionMapper
    public RestResponse<String> mapException(UnknownCheeseException x) {
        return RestResponse.status(Response.Status.NOT_FOUND, "Unknown cheese: " + x.name);
    }

    @GET
    public String findFromage(String fromage) {
        if(fromage == null)
            // send a 400
            throw new BadRequestException();
        return cheeses.findCheese(fromage);
    }
}
exception mappers defined in REST endpoint classes will only be called if the exception is thrown in the same class. If you want to define global exception mappers, simply define them outside a REST endpoint class:
package org.acme.rest;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.RestResponse;

class ExceptionMappers {
    @ServerExceptionMapper
    public RestResponse<String> mapException(UnknownCheeseException x) {
        return RestResponse.status(Response.Status.NOT_FOUND, "Unknown cheese: " + x.name);
    }
}

You can also declare exception mappers in the JAX-RS way.

Your exception mapper may declare any of the following parameter types:

Table 8. Table Exception mapper parameters
Type Usage

An exception type

Defines the exception type you want to handle

Any of the Context objects

ContainerRequestContext

A context object to access the current request

It may declare any of the following return types:

Table 9. Table Exception mapper return types
Type Usage

RestResponse or Response

The response to send to the client when the exception occurs

Uni<RestResponse> or Uni<Response>

An asynchronous response to send to the client when the exception occurs

Request or response filters

You can declare functions which are invoked in the following phases of the request processing:

  • Before the endpoint method is identified: pre-routing request filter

  • After routing, but before the endpoint method is called: normal request filter

  • After the endpoint method is called: response filter

These filters allow you to do various things such as examine the request URI, HTTP method, influence routing, look or change request headers, abort the request, or modify the response.

Request filters can be declared with the @ServerRequestFilter annotation:

import java.util.Optional;

class Filters {

    @ServerRequestFilter(preMatching = true)
    public void preMatchingFilter(ContainerRequestContext requestContext) {
        // make sure we don't lose cheese lovers
        if("yes".equals(requestContext.getHeaderString("Cheese"))) {
            requestContext.setRequestUri(URI.create("/cheese"));
        }
    }

    @ServerRequestFilter
    public Optional<RestResponse<Void>> getFilter(ContainerRequestContext ctx) {
        // only allow GET methods for now
        if(ctx.getMethod().equals(HttpMethod.GET)) {
            return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
        }
        return Optional.empty();
    }
}

Request filters are usually executed on the same thread that the method that handles the request will be executed. That means that if the method servicing the request is annotated with @Blocking, then the filters will also be run on the worker thread. If the method is annotated with @NonBlocking (or is not annotated at all), then the filters will also be run on the same event-loop thread.

If however a filter needs to be run on the event-loop despite the fact that the method servicing the request will be run on a worker thread, then @ServerRequestFilter(nonBlocking=true) can be used. Note however, that these filters need to be run before any filter that does not use that setting and would run on a worker thread.

Similarly, response filters can be declared with the @ServerResponseFilter annotation:

class Filters {
    @ServerResponseFilter
    public void getFilter(ContainerResponseContext responseContext) {
        Object entity = responseContext.getEntity();
        if(entity instanceof String) {
            // make it shout
            responseContext.setEntity(((String)entity).toUpperCase());
        }
    }
}

Your filters may declare any of the following parameter types:

Table 10. Table Filter parameters
Type Usage

Any of the Context objects

ContainerRequestContext

A context object to access the current request

ContainerResponseContext

A context object to access the current response

Throwable

Any thrown exception, or null (only for response filters)

It may declare any of the following return types:

Table 11. Table Filter return types
Type Usage

RestResponse<?> or Response

The response to send to the client instead of continuing the filter chain, or null if the filter chain should proceed

Optional<RestResponse<?>> or Optional<Response>

An optional response to send to the client instead of continuing the filter chain, or an empty value if the filter chain should proceed

Uni<RestResponse<?>> or Uni<Response>

An asynchronous response to send to the client instead of continuing the filter chain, or null if the filter chain should proceed

You can restrict the Resource methods for which a filter runs, by using @NameBinding meta-annotations.

Readers and Writers: mapping entities and HTTP bodies

Whenever your endpoint methods return a object (of when they return a RestResponse<?> or Response with an entity), RESTEasy Reactive will look for a way to map that into an HTTP response body.

Similarly, whenever your endpoint method takes an object as parameter, we will look for a way to map the HTTP request body into that object.

This is done via a pluggable system of MessageBodyReader and MessageBodyWriter interfaces, which are responsible for defining which Java type they map from/to, for which media types, and how they turn HTTP bodies to/from Java instances of that type.

For example, if we have our own FroMage type on our endpoint:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;

class FroMage {
    public String name;

    public FroMage(String name) {
        this.name = name;
    }
}

@Path("cheese")
public class Endpoint {

    @GET
    public FroMage sayCheese() {
        return new FroMage("Cheeeeeese");
    }

    @PUT
    public void addCheese(FroMage fromage) {
        System.err.println("Received a new cheese: " + fromage.name);
    }
}

Then we can define how to read and write it with our body reader/writers, annotated with @Provider:

package org.acme.rest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
public class FroMageBodyHandler implements MessageBodyReader<FroMage>,
                                           MessageBodyWriter<FroMage> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) {
        return type == FroMage.class;
    }

    @Override
    public void writeTo(FroMage t, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders,
                        OutputStream entityStream)
            throws IOException, WebApplicationException {
        entityStream.write(("[FroMageV1]" + t.name)
                           .getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType) {
        return type == FroMage.class;
    }

    @Override
    public FroMage readFrom(Class<FroMage> type, Type genericType,
                            Annotation[] annotations, MediaType mediaType,
                            MultivaluedMap<String, String> httpHeaders,
                            InputStream entityStream)
            throws IOException, WebApplicationException {
        String body = new String(entityStream.readAllBytes(), StandardCharsets.UTF_8);
        if(body.startsWith("[FroMageV1]"))
            return new FroMage(body.substring(11));
        throw new IOException("Invalid fromage: " + body);
    }

}

If you want to get the most performance our of your writer, you can extend the ServerMessageBodyWriter instead of MessageBodyWriter where you will be able to use less reflection and bypass the blocking IO layer:

package org.acme.rest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;

@Provider
public class FroMageBodyHandler implements MessageBodyReader<FroMage>,
                                           ServerMessageBodyWriter<FroMage> {

    // …

    @Override
    public boolean isWriteable(Class<?> type, ResteasyReactiveResourceInfo target,
                               MediaType mediaType) {
        return type == FroMage.class;
    }

    @Override
    public void writeResponse(FroMage t, ServerRequestContext context)
      throws WebApplicationException, IOException {
        context.serverResponse().end("[FroMageV1]" + t.name);
    }
}
You can restrict which content-types your reader/writer apply to by adding Consumes/Produces annotations on your provider class.

Reader and Writer interceptors

Just as you can intercept requests and responses, you can also intercept readers and writers, by extending the ReaderInterceptor or WriterInterceptor on a class annotated with @Provider.

If we look at this endpoint:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;

@Path("cheese")
public class Endpoint {

    @GET
    public String sayCheese() {
        return "Cheeeeeese";
    }

    @PUT
    public void addCheese(String fromage) {
        System.err.println("Received a new cheese: " + fromage);
    }
}

We can add reader and writer interceptors like this:

package org.acme.rest;

import java.io.IOException;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

@Provider
public class FroMageIOInterceptor implements ReaderInterceptor, WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
      throws IOException, WebApplicationException {
        System.err.println("Before writing " + context.getEntity());
        context.proceed();
        System.err.println("After writing " + context.getEntity());
    }

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context)
      throws IOException, WebApplicationException {
        System.err.println("Before reading " + context.getGenericType());
        Object entity = context.proceed();
        System.err.println("After reading " + entity);
        return entity;
    }
}

Parameter mapping

All Request Parameters can be declared as String, but also any of the following types:

  • Types for which a ParamConverter is available via a registered ParamConverterProvider.

  • Primitive types.

  • Types that have a constructor that accepts a single String argument.

  • Types that have a static method named valueOf or fromString with a single String argument that return an instance of the type. If both methods are present then valueOf will be used unless the type is an enum in which case fromString will be used.

  • List<T>, Set<T>, or SortedSet<T>, where T satisfies any above criterion.

The following example illustrates all those possibilities:

package org.acme.rest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.RestQuery;

@Provider
class MyConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
                                              Annotation[] annotations) {
        // declare a converter for this type
        if(rawType == Converter.class) {
            return (ParamConverter<T>) new MyConverter();
        }
        return null;
    }

}

// this is my custom converter
class MyConverter implements ParamConverter<Converter> {

    @Override
    public Converter fromString(String value) {
        return new Converter(value);
    }

    @Override
    public String toString(Converter value) {
        return value.value;
    }

}

// this uses a converter
class Converter {
    String value;
    Converter(String value) {
        this.value = value;
    }
}

class Constructor {
    String value;
    // this will use the constructor
    public Constructor(String value) {
        this.value = value;
    }
}

class ValueOf {
    String value;
    private ValueOf(String value) {
        this.value = value;
    }
    // this will use the valueOf method
    public static ValueOf valueOf(String value) {
        return new ValueOf(value);
    }
}

@Path("hello")
public class Endpoint {

    @Path("{converter}/{constructor}/{primitive}/{valueOf}")
    @GET
    public String convertions(Converter converter, Constructor constructor,
                              int primitive, ValueOf valueOf,
                              @RestQuery List<Constructor> list) {
        return converter + "/" + constructor + "/" + primitive
               + "/" + valueOf + "/" + list;
    }
}

Handling dates

RESTEasy Reactive supports the use of the implementations of java.time.Temporal (like java.time.LocalDateTime) as query, path or form params. Furthermore it provides the @org.jboss.resteasy.reactive.DateFormat annotation which can be used to set a custom expected pattern (otherwise the JDK’s default format for each type is used implicitly).

Preconditions

HTTP allows requests to be conditional, based on a number of conditions, such as:

  • Date of last resource modification

  • A resource tag, similar to a hash code of the resource to designate its state or version

Let’s see how you can do conditional request validation using the Request context object:

package org.acme.rest;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

@Path("conditional")
public class Endpoint {

    // It's important to keep our date on seconds because that's how it's sent to the
    // user in the Last-Modified header
    private Date date = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
    private int version = 1;
    private EntityTag tag = new EntityTag("v1");
    private String resource = "Some resource";

    @GET
    public Response get(Request request) {
        // first evaluate preconditions
        ResponseBuilder conditionalResponse = request.evaluatePreconditions(date, tag);
        if(conditionalResponse != null)
            return conditionalResponse.build();
        // preconditions are OK
        return Response.ok(resource)
                .lastModified(date)
                .tag(tag)
                .build();
    }

    @PUT
    public Response put(Request request, String body) {
        // first evaluate preconditions
        ResponseBuilder conditionalResponse = request.evaluatePreconditions(date, tag);
        if(conditionalResponse != null)
            return conditionalResponse.build();
        // preconditions are OK, we can update our resource
        resource = body;
        date = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
        version++;
        tag = new EntityTag("v" + version);
        return Response.ok(resource)
                .lastModified(date)
                .tag(tag)
                .build();
    }
}

When we call GET /conditional the first time, we will get this response:

HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
ETag: "v1"
Last-Modified: Wed, 09 Dec 2020 16:10:19 GMT
Content-Length: 13

Some resource

So now if we want to check if we need to fetch a new version, we can make the following request:

GET /conditional HTTP/1.1
Host: localhost:8080
If-Modified-Since: Wed, 09 Dec 2020 16:10:19 GMT

And we would get the following response:

HTTP/1.1 304 Not Modified

Because the resource has not been modified since that date. This saves on sending the resource, but can also help your users detect concurrent modification, for example, let’s suppose that one client wants to update the resource, but another user has modified it since. You can follow the previous GET request with this update:

PUT /conditional HTTP/1.1
Host: localhost:8080
If-Unmodified-Since: Wed, 09 Dec 2020 16:25:43 GMT
If-Match: v1
Content-Length: 8
Content-Type: text/plain

newstuff

And if some other user has modified the resource between your GET and your PUT you would get this answer back:

HTTP/1.1 412 Precondition Failed
ETag: "v2"
Content-Length: 0

Negotiation

One of the main ideas of REST (and HTTP) is that your resource is independent from its representation, and that both the client and server are free to represent their resources in as many media types as they want. This allows the server to declare support for multiple representations and let the client declare which ones it supports and get served something appropriate.

The following endpoint supports serving cheese in plain text or JSON:

package org.acme.rest;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.fasterxml.jackson.annotation.JsonCreator;

class FroMage {
    public String name;
    @JsonCreator
    public FroMage(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Cheese: " + name;
    }
}

@Path("negotiated")
public class Endpoint {

    @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
    @GET
    public FroMage get() {
        return new FroMage("Morbier");
    }

    @Consumes(MediaType.TEXT_PLAIN)
    @PUT
    public FroMage putString(String cheese) {
        return new FroMage(cheese);
    }

    @Consumes(MediaType.APPLICATION_JSON)
    @PUT
    public FroMage putJson(FroMage fromage) {
        return fromage;
    }
}

The user will be able to select which representation it gets with the Accept header, in the case of JSON:

> GET /negotiated HTTP/1.1
> Host: localhost:8080
> Accept: application/json

< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 18
<
< {"name":"Morbier"}

And for text:

> GET /negotiated HTTP/1.1
> Host: localhost:8080
> Accept: text/plain
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 15
<
< Cheese: Morbier

Similarly, you can PUT two different representations. JSON:

> PUT /negotiated HTTP/1.1
> Host: localhost:8080
> Content-Type: application/json
> Content-Length: 16
>
> {"name": "brie"}

< HTTP/1.1 200 OK
< Content-Type: application/json;charset=UTF-8
< Content-Length: 15
<
< {"name":"brie"}

Or plain text:

> PUT /negotiated HTTP/1.1
> Host: localhost:8080
> Content-Type: text/plain
> Content-Length: 9
>
> roquefort

< HTTP/1.1 200 OK
< Content-Type: application/json;charset=UTF-8
< Content-Length: 20
<
< {"name":"roquefort"}

Include/Exclude JAX-RS classes with build time conditions

Quarkus enables the inclusion or exclusion of JAX-RS Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans. Thus, the various JAX-RS classes can be annotated with profile conditions (@io.quarkus.arc.profile.IfBuildProfile or @io.quarkus.arc.profile.UnlessBuildProfile) and/or with property conditions (io.quarkus.arc.properties.IfBuildProperty or io.quarkus.arc.properties.UnlessBuildProperty) to indicate to Quarkus at build time under which conditions these JAX-RS classes should be included.

In the following example, Quarkus includes the endpoint sayHello if and only if the build profile app1 has been enabled.

@IfBuildProfile("app1")
public class ResourceForApp1Only {

    @GET
    @Path("sayHello")
    public String sayHello() {
        return "hello";
     }
}

Please note that if a JAX-RS Application has been detected and the method getClasses() and/or getSingletons() has/have been overridden, Quarkus will ignore the build time conditions and consider only what has been defined in the JAX-RS Application.

RESTEasy Reactive client

In addition to the Server side, RESTEasy Reactive comes with a new MicroProfile Rest Client implementation that is non-blocking at its core.

Please note that the quarkus-rest-client extension may not be used with RESTEasy Reactive, use quarkus-rest-client-reactive instead.

See the REST Client Reactive Guide for more information about the reactive REST client.


1. Unless it is a URI template parameter or a context object.