Quarkus - Writing JSON REST Services

    In this guide, we see how you can get your REST services to consume and produce JSON payloads.

    To complete this guide, you need:

    • less than 15 minutes

    • an IDE

    • JDK 1.8+ installed with configured appropriately

    • Apache Maven 3.5.3+

    Architecture

    The application built in this guide is quite simple: the user can add elements in a list using a form and the list is updated.

    All the information between the browser and the server are formatted as JSON.

    Solution

    We recommend that you follow the instructions in the next sections and create the application step by step.However, you can go right to the completed example.

    Clone the Git repository: git clone , or download an archive.

    The solution is located in the rest-json-quickstart .

    Creating the Maven project

    First, we need a new project. Create a new project with the following command:

    This command generates a Maven structure importing the RESTEasy/JAX-RS and JSON-B extensions.

    Quarkus also supports so, if you prefer Jackson over JSON-B, you can create a project relying on the RESTEasy Jackson extension instead:

    1. mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create \
    2. -DprojectGroupId=org.acme \
    3. -DprojectArtifactId=rest-json \
    4. -DclassName="org.acme.rest.json.FruitResource" \
    5. -Dpath="/fruits" \
    6. -Dextensions="resteasy-jackson"
    7. cd rest-json-quickstart

    To improve user experience, Quarkus registers the three Jackson Java 8 modules so you don’t need to do it manually.

    In this example, we will create an application to manage a list of fruits.

    First, let’s create the Fruit bean as follows:

    1. package org.acme.rest.json;
    2. public class Fruit {
    3. public String name;
    4. public String description;
    5. public Fruit() {
    6. }
    7. public Fruit(String name, String description) {
    8. this.name = name;
    9. this.description = description;
    10. }
    11. }

    Nothing fancy. One important thing to note is that having a default constructor is required by the JSON serialization layer.

    Now, edit the org.acme.rest.json.FruitResource class as follows:

    1. package org.acme.rest.json;
    2. import java.util.Collections;
    3. import java.util.LinkedHashMap;
    4. import java.util.Set;
    5. import javax.ws.rs.Consumes;
    6. import javax.ws.rs.DELETE;
    7. import javax.ws.rs.GET;
    8. import javax.ws.rs.POST;
    9. import javax.ws.rs.Path;
    10. import javax.ws.rs.Produces;
    11. import javax.ws.rs.core.MediaType;
    12. @Path("/fruits")
    13. @Produces(MediaType.APPLICATION_JSON)
    14. @Consumes(MediaType.APPLICATION_JSON)
    15. public class FruitResource {
    16. public FruitResource() {
    17. fruits.add(new Fruit("Apple", "Winter fruit"));
    18. fruits.add(new Fruit("Pineapple", "Tropical fruit"));
    19. }
    20. @GET
    21. public Set<Fruit> list() {
    22. return fruits;
    23. }
    24. @POST
    25. public Set<Fruit> add(Fruit fruit) {
    26. fruits.add(fruit);
    27. return fruits;
    28. }
    29. @DELETE
    30. public Set<Fruit> delete(Fruit fruit) {
    31. fruits.removeIf(existingFruit -> existingFruit.name.contentEquals(fruit.name));
    32. return fruits;
    33. }
    34. }

    The implementation is pretty straightforward and you just need to define your endpoints using the JAX-RS annotations.

    The Fruit objects will be automatically serialized/deserialized by or Jackson,depending on the extension you chose when initializing the project.

    JSON-B

    Quarkus makes it very easy to configure various JSON-B settings via CDI beans. The simplest (and suggested) approach is to define a CDI bean of type io.quarkus.jsonb.JsonbConfigCustomizerinside of which any JSON-B configuration can be applied.

    If for example a custom serializer named FooSerializer for type com.example.Foo needs to be registered with JSON-B, the addition of a bean like the following would suffice:

    Jackson

    As stated above, Quarkus provides the option of using instead of JSON-B via the use of the quarkus-resteasy-jackson extension.

    Following the same approach as described in the previous section, Jackson’s ObjectMapper can be configured using a io.quarkus.jackson.ObjectMapperCustomizer bean.An example where a custom module needs to be registered would like so:

    1. import com.fasterxml.jackson.databind.ObjectMapper;
    2. import io.quarkus.jackson.ObjectMapperCustomizer;
    3. import javax.inject.Singleton;
    4. @Singleton
    5. public class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer {
    6. public void customize(ObjectMapper mapper) {
    7. mapper.registerModule(new CustomModule());
    8. }
    9. }

    Users can even provide their own ObjectMapper bean if they so choose.If this is done, it is very important to manually inject and apply all io.quarkus.jackson.ObjectMapperCustomizer beans in the CDI producer that produces ObjectMapper.Failure to do so will prevent Jackson specific customizations provided by various extensions from being applied.

    Creating a frontend

    Now let’s add a simple web page to interact with our FruitResource.Quarkus automatically serves static resources located under the META-INF/resources directory.In the src/main/resources/META-INF/resources directory, add a fruits.html file with the content from this fruits.html file in it.

    You can now interact with your REST service:

    • start Quarkus with ./mvnw compile quarkus:dev

    • open a browser to

    • add new fruits to the list via the form

    Building a native executable

    You can build a native executable with the usual command ./mvnw package -Pnative.

    Running it is as simple as executing ./target/rest-json-quickstart-1.0-SNAPSHOT-runner.

    You can then point your browser to http://localhost:8080/fruits.html and use your application.

    About serialization

    JSON serialization libraries use Java reflection to get the properties of an object and serialize them.

    When using native executables with GraalVM, all classes that will be used with reflection need to be registered.The good news is that Quarkus does that work for you most of the time.So far, we haven’t registered any class, not even Fruit, for reflection usage and everything is working fine.

    Quarkus performs some magic when it is capable of inferring the serialized types from the REST methods.When you have the following REST method, Quarkus determines that Fruit will be serialized:

    1. @GET
    2. @Produces("application/json")
    3. public List<Fruit> list() {
    4. // ...
    5. }

    Quarkus does that for you automatically by analyzing the REST methods at build timeand that’s why we didn’t need any reflection registration in the first part of this guide.

    Another common pattern in the JAX-RS world is to use the Response object.Response comes with some nice perks:

    • you can return different entity types depending on what happens in your method (a Legume or an Error for instance);

    • you can set the attributes of the Response (the status comes to mind in the case of an error).

    Your REST method then looks like this:

    1. @GET
    2. @Produces("application/json")
    3. public Response list() {
    4. // ...
    5. }

    It is not possible for Quarkus to determine at build time the type included in the Response as the information is not available.In this case, Quarkus won’t be able to automatically register for reflection the required classes.

    This leads us to our next section.

    Let’s create the Legume class which will be serialized as JSON, following the same model as for our Fruit class:

    Now let’s create a LegumeResource REST service with only one method which returns the list of legumes.

    1. package org.acme.rest.json;
    2. import java.util.Collections;
    3. import java.util.LinkedHashSet;
    4. import java.util.Set;
    5. import javax.ws.rs.Consumes;
    6. import javax.ws.rs.GET;
    7. import javax.ws.rs.Path;
    8. import javax.ws.rs.Produces;
    9. import javax.ws.rs.core.MediaType;
    10. import javax.ws.rs.core.Response;
    11. @Path("/legumes")
    12. @Produces(MediaType.APPLICATION_JSON)
    13. public class LegumeResource {
    14. private Set<Legume> legumes = Collections.synchronizedSet(new LinkedHashSet<>());
    15. public LegumeResource() {
    16. legumes.add(new Legume("Carrot", "Root vegetable, usually orange"));
    17. legumes.add(new Legume("Zucchini", "Summer squash"));
    18. }
    19. @GET
    20. public Response list() {
    21. return Response.ok(legumes).build();
    22. }
    23. }

    Now let’s add a simple web page to display our list of legumes.In the src/main/resources/META-INF/resources directory, add a legumes.html file with the content from this file in it.

    Open a browser to http://localhost:8080/legumes.html and you will see our list of legumes.

    The interesting part starts when running the application as a native executable:

    • create the native executable with ./mvnw package -Pnative.

    • execute it with ./target/rest-json-quickstart-1.0-SNAPSHOT-runner

    • open a browser and go to

    No legumes there.

    As mentioned above, the issue is that Quarkus was not able to determine the Legume class will require some reflection by analyzing the REST endpoints.The JSON serialization library tries to get the list of fields of Legume and gets an empty list so it does not serialize the fields' data.

    We can register Legume for reflection manually by adding the @RegisterForReflection annotation on our Legume class:

    1. @RegisterForReflection
    2. public class Legume {
    3. // ...
    4. }

    Let’s do that and follow the same steps as before:

    • hit Ctrl+C to stop the application

    • create the native executable with ./mvnw package -Pnative.

    • execute it with ./target/rest-json-quickstart-1.0-SNAPSHOT-runner

    • open a browser and go to http://localhost:8080/legumes.html

    This time, you can see our list of legumes.

    HTTP filters and interceptors

    Both HTTP request and response can be intercepted by providing ContainerRequestFilter or ContainerResponseFilterimplementations respectively. These filters are suitable for processing the metadata associated with a message: HTTPheaders, query parameters, media type, and other metadata. They also have the capability to abort the requestprocessing, for instance when the user does not have the permissions to access the endpoint.

    Let’s use ContainerRequestFilter to add logging capability to our service. We can do that by implementingContainerRequestFilter and annotating it with the @Provider annotation:

    1. package org.acme.rest.json;
    2. import io.vertx.core.http.HttpServerRequest;
    3. import org.jboss.logging.Logger;
    4. import javax.ws.rs.container.ContainerRequestContext;
    5. import javax.ws.rs.container.ContainerRequestFilter;
    6. import javax.ws.rs.core.Context;
    7. import javax.ws.rs.core.UriInfo;
    8. import javax.ws.rs.ext.Provider;
    9. @Provider
    10. public class LoggingFilter implements ContainerRequestFilter {
    11. private static final Logger LOG = Logger.getLogger(LoggingFilter.class);
    12. @Context
    13. UriInfo info;
    14. @Context
    15. HttpServerRequest request;
    16. @Override
    17. public void filter(ContainerRequestContext context) {
    18. final String method = context.getMethod();
    19. final String path = info.getPath();
    20. final String address = request.remoteAddress().toString();
    21. LOG.infof("Request %s %s from IP %s", method, path, address);
    22. }
    23. }

    Now, whenever a REST method is invoked, the request will be logged into the console:

    CORS filter

    (CORS) is a mechanism thatallows restricted resources on a web page to be requested from another domain outside the domain from which the first resourcewas served.

    Quarkus comes with a CORS filter. Read the HTTP Reference Documentation to learnhow to use it.

    GZip Support

    Quarkus comes with GZip support (even though it is not enabled by default). The following configuration knobs allow to configure GZip support.

    1. quarkus.resteasy.gzip.enabled=true (1)

    Creating JSON REST services with Quarkus is easy as it relies on proven and well known technologies.

    As usual, Quarkus further simplifies things under the hood when running your application as a native executable.