Coding Filters and Interceptors for your RESTFul services

Jakarta Restful Web Services includes an Interceptor API that allows developers to intercept request and response processing. This allows addressing some advanced concepts like authentication, caching, and compressing without polluting application code. Jakarta Restful Web Services define two broad categories to intercept requests and responses: Filters and Entity Interceptors.

  • Filters are used to modify or process incoming and outgoing request/response headers. They can be applied both on the server side and on the client side of the communication.

  • Interceptors deal with marshalling and unmarshalling the content of HTTP message bodies. With this purpose, they wrap around the execution of javax.ws.rs.ext.MessageBodyReader and javax.ws.rs.ext.MessageBodyWriter instances.

In this tutorial we will learn how to include Interceptors in your RESTFul Web services.

REST Server Side Filters

On the server side, you can define two types of filters:

  • ContainerRequestFilters: these kinds of filters are executed before your REST resource method is invoked.

  • ContainerResponseFilters: they execute after your REST resource method is invoked.

The following example shows how to dump the HTTP Header before the actual method invocation using a ContainerRequestFilter:

@Provider
public class LoggingFilter implements ContainerRequestFilter  {
    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        System.out.println(crc.getMethod() + " " + crc.getUriInfo().getAbsolutePath());
        for (String key : crc.getHeaders().keySet()) {
            System.out.println("[REST Logging] " +key + ": " + crc.getHeaders().get(key));
        }
    }
}

The first thing to note here is the @Provider annotation, which is used to automatically discover and register the Filter. Within the filter callback method, it is possible to go through the HTTP Headers before the actual method invocation.

Request filters can do much more than logging anyway. Actually, the ContainerRequestContext provides access to the javax.ws.rs.core.SecurityContext interface, containing security related information. In the following code, we are checking that the authenticated user is included in the specified role named “secureuser”:

@Provider
public class SecurityRequestFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext)
                    throws IOException {
        final SecurityContext securityContext =
                    requestContext.getSecurityContext();
        if (securityContext == null ||
                    !securityContext.isUserInRole("secureuser")) {
                          requestContext.abortWith(Response
                              .status(Response.Status.UNAUTHORIZED)
                              .entity("User unauthorized!").build());
        }
    }
}

The above examples are meant to be Post Matching request filters, which means that filters are be applied only after a suitable resource method has been selected to process the actual request.

Since Post Matching filters are invoked when a particular resource method has already been selected, such filters cannot influence the resource method matching process. 

To overcome this caveat, there is a possibility to mark a server request filter with the @PreMatching annotation. PreMatching filters are request filters that are executed before the request matching is started. The advantage of PreMatching request filters is that they have the possibility to influence which method will be matched.

A PreMatching request filter example follows here:

@PreMatching
@Provider
public class PreMatchingFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext)
                        throws IOException {

        if (requestContext.getMethod().equals("PUT")) {
            requestContext.setMethod("POST");
        }
    }
}

In the above example, we are changing the request method from PUT to POST to circumvent a limitation that may arise from firewalls configuration.

After the resource class method is executed, all ContainerResponseFilters will be triggered. These filters allow you to modify the outgoing response before it is marshalled and sent to the client.

In the following example, we are showing how to use a ContainerResponseFilter to set a Cache-Control header, which disables caching:

@Provider
public class CachingFilter implements  ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext crc, ContainerResponseContext crc1) throws IOException {
            if (crc.getMethod().equals("GET")) {
                    crc.getHeaders().add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");

                    crc.getHeaders().add("Expires", "-1");
            }
    }
}

REST Client Side Filters

Filters can be applied as well on the client side in order to perform some custom actions with the client request or the response received from the target resource. There can be two types of Client filters that can be registered:

  • Client Request Filters: are executed before your HTTP request is sent to the server.
  • Client Response Filters: are executed after a response is received from the server, but before the response body is unmarshalled.

Client side filters, just like server filters, can be implemented in distinct classes or in a single class.

In terms of workflow, after the ClientRequestFilter is executed, the request (if not aborted) is then physically sent over to the server side. Once a response is received back from the server, the client response filters (ClientResponseFilter) are executed; that’s the last option for varying the returned response.

We will show here how to set up a Client filter, which is implementing both type of interfaces (ClientRequestFilter and ClientResponseFilter) in a single class. This filter aims to demonstrate how to modify the initial HTTP request by adding to it a Basic Authorization header containing an username and a password. The method filter(ClientRequestContext req) contains the logic for modifying the HTTP Request

Conversely, we are dumping the HTTP Headers using the ClientResponseFilter into the method filter(ClientRequestContext arg0, ClientResponseContext crc1).

The class implementation follows here:

public class ClientFilter implements ClientRequestFilter,ClientResponseFilter {
        private final String username;
        private final String password;

        public ClientFilter(String username, String password) {
                this.username = username;
                this.password = password;
        }
        public void filter(ClientRequestContext req) {
                String token = username + ":" + password;

                String base64Token = new String
                            (Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8)));
               req.getHeaders().add("Authorization", "Basic " + base64Token);
                System.out.println("Added to HTTP Request Authorization ["+base64Token+"]");
        }

        @Override
        public void filter(ClientRequestContext arg0, ClientResponseContext crc1)
                        throws IOException {

                for (String key : crc1.getHeaders().keySet()) {
                        System.out.println("Response Header: " +crc1.getHeaders().get(key));
                }

        }
}

One more difference from Client Filters and Server Filters is that Client Filter needs to be registered programmatically on the Client object in order to be triggered. In the following example, we are showing how to register a ClientFilter by calling the register method and passing as parameter your Client Filter implementation:

Client client = ClientBuilder.newClient();
client.register(new ClientFilter("user","password"));

Reader and Writer Interceptors

While filters are primarily intended to manipulate request and response parameters (like HTTP header) interceptors can be used to manipulate entities, by changing their input/output streams. For example, you might need to compress or encode the entity body of a client request. There are two kinds of interceptors:

  • Reader interceptors are used to manipulate the request entity stream on the server side and response entity stream on the client side. Reader Interceptors wrap around the execution of a MessageBodyReader.
  • Writer interceptors are used triggered when the entity is written to the “wire”. On the server side that happens when writing out a response entity and on the client side when writing request entity to be sent out to the server.

The following example shows how to use a Writer interceptor to enable GZIP compression on the entity body:

@Provider
public class ZipWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

Within this example, the Writer interceptor gets an output stream from the WriterInterceptorContext and sets a new one, which is wrapped by a GZIP output stream. Because of it, the entity bytes will be written to the GZIPOutputStream that will compress the data and write it to the original output stream.

One more thing that you might have noticed is the proceed() invocation which is used to invoke the next interceptor in the chain and it’s necessary otherwise the entity would not be written.

Interceptors are different from Filters since Filters are not able to directly invoke the next Filter in the chain as Interceptors do. 

Let’s see now an example of ReaderInterceptor that could work together with the Writer interceptor that we have already coded:

@Provider
public class ZipReaderInterceptor implements ReaderInterceptor {
        @Override
        public Object aroundReadFrom(ReaderInterceptorContext context)
                        throws IOException, WebApplicationException {
                 final InputStream inputStream = context.getInputStream();
                 context.setInputStream(new GZIPInputStream(inputStream));
                 return context.proceed();
        }
}

In this example, the ZipReaderInterceptor collects the input stream and wraps it with the GZIPInputStream. Because of that, further reads from the entity stream will cause the data to be decompressed by this stream.

As for the writer counterpart, the proceed() method internally calls the wrapped interceptor which must also return an entity. The proceed method invoked from the last interceptor in the chain calls message body reader which deserializes the entity end returns it.

You can check the Content length of your REST Service with ZIP interceptors by means of your Browser tools. For example, Chrome contains a Developer’s tools menu, which allows displaying several elements concerning the HTTP Request, such as the Network data which is going across the net:

References

This tutorial is an excerpt from the Practical Enterprise Development guide which is an in-depth guide on Jakarta EE Development / Microprofile applications.

The source code of the book is hosted on GitHub: https://github.com/fmarchioni/practical-enterprise-development/tree/master/code/jaxrs/ee-rest-filters

The Interceptors offered by RESTEasy 2.x

If you are running an older JBoss environment (JBoss EAP 6 / JBoss AS 7) then you have to rely on RESTEasy’s own Interceptors. RESTEasy 2.3 offers 3 types of interceptors:

  • MessageBodyReader and MessageBodyWriter Interceptors: Invoked around the HTTP Request body marshalling/unmarshalling;
  • Method execution interceptors: Invoked before(PreProcessInterceptor) and after (PostProcessInterceptor) the JAX-RS method execution;
  • Client side interceptors: Invoked before and after the client performed the request(if you call the ClientExecutionContext proceed() method it will perform the request). For more information about RESTEasy Client API, See this tutorial: JAX-RS client API tutorial

By default these interceptor will be invoked to all requests that comes to your JAX-RS service, but you can implement the interface AcceptedByMethod and read the service information(HTTP Method, method to be invoked, etc) to chose if your interceptor should be executed, or not.
That’s enough, that’s what we need to know for going on this article. If you want to know more about this, please check the RESTEasy documentation..

Adding Logging

To gather information from the client and information of the method which will be executed, we created a simple Server Interceptor which implements PreProcessInterceptor interface. This means that it will be called before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class LoggingInterceptor implements PreProcessInterceptor {
    Logger logger = Logger.getLogger(LoggingInterceptor.class);

    @Context
    HttpServletRequest servletRequest;

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        String methodName = resourceMethod.getMethod().getName();
        logger.info("Receiving request from: " + servletRequest.getRemoteAddr());
        logger.info("Attempt to invoke method \"" + methodName + "\"");
        if (methodName.equals("calculateAllBasicTrigonometricFunctions")) {
            logger.info("\tCalculate will be performed with parameters:");
            logger.info("\tAdjacent: "
                    + request.getFormParameters().get("adjacent"));
            logger.info("\tOpposite: "
                    + request.getFormParameters().get("opposite"));
            logger.info("\tHypotenusa: "
                    + request.getFormParameters().get("hypotenusa"));
        }
        if (methodName.equals("history")) {
            logger.info("Retrieving history...");
        }
        if (methodName.equals("clearAll")) {
            logger.info("User " + servletRequest.getRemoteUser()
                    + " is trying to clear the history...");
        }
        return null;
    }
}

The implemented method receives parameters which contains information about the request done and Java information about the JAX-RS method. Our LoggingInterceptor simple catch request and method information and then log it. Notice we can also inject resources into this interceptor, in this case we are injecting the ServletRequest. In the method implementation,

Adding RESTEasy Validation

As the basic trigonometric functions need all the right triangle information (adjacent, opposite and hypotenusa), we will perform validation before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import static java.lang.Double.parseDouble;
import static java.lang.Math.pow;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    private double adjacent;
    private double opposite;
    private double hypotenusa;

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("calculateAllBasicTrigonometricFunctions");
    }

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        // start server response as null and perform the validations, if it gets
        // some value, it will be a valid response and the interceptor will stop
        // the request
        ServerResponse response = null;
        try {
            adjacent = parseDouble(request.getFormParameters()
                    .get("adjacent").get(0));
            opposite = Double.parseDouble(request.getFormParameters()
                    .get("opposite").get(0));
            hypotenusa = Double.parseDouble(request.getFormParameters()
                    .get("hypotenusa").get(0));

            // Pythagoras
            boolean isValid = pow(adjacent, 2) + pow(opposite, 2) == pow(
                    hypotenusa, 2);
            // If it is not valid we will create a response with an appropriated
            // HTTP Code
            if (!isValid) {
                response = new ServerResponse("Not a valid right triangle",
                        400, new Headers<Object>());
            }
        } catch (Exception e) {
            response = new ServerResponse(
                    "Please verify the sent parameters, can't convert for use",
                    400, new Headers<Object>());
        }
        return response;
    }
}

Notice this interceptor is implementing an the interface AcceptedByMethod. We also have to implement the method accept, where we receive the target method information and we can decide whether  if this interceptor will be executed or not. Also  notice that according the data sent by the client we will create a response which can stop the execution. If we return null the execution will flow normally, if we return a Server Response, the execution will stop in this method and the request will not get into your JAX-RS method neither in order interceptors.

RESTEasy Security Checking

For some methods we want to check security before executing it. For example, to clean the history we want to assure that a specific user can do this. Of course we could do this using Jaas or some application server built in mechanism. In this case we will use an interceptor that will check the presence of a parameter in the request and validate it against a fixed String.

package org.mastertheboss.resteasy.resources.interceptors;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("clearAll");
    }

    public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
            throws Failure, WebApplicationException {
        ServerResponse response = null;

        String username = request.getFormParameters().get("username").get(0);
        // very simple security validation
        if (username == null || username.isEmpty()) {
            response = new ServerResponse(
                    "To access this method you need to inform an username",
                    401, new Headers<Object>());
        } else if (!"john".equals(username)) {
            response = new ServerResponse("User \"" + username
                    + "\" is not authorized to access this method.", 403,
                    new Headers<Object>());
        }
        return response;
    }
}

The logic of the interceptor is simple, we just create a server response according the content of the parameter “username”.

Running the code

You can download the attached a Maven project which contains the interceptors described in this article. You can build the code using the command mvn clean install and deploy it in JBoss AS 7. When you access the root context of this application it will be displayed a very simple HTML page aimed to test the JAX-RS methods we demonstrated.

Getting started with RESTEasy and WildFly

A REST API, also known as a RESTful API, is an application programming interface that conforms to the constraints of the REST architectural style. When a client sends a request using a RESTful API, it transfers a representative state of the resource to the requester or endpoint. The information can use several formats, such as: JSON, HTML, XML, or plain text.

Here are they key principles of RESTFul Web services:

  • Addressable resources: means that you map each resource with a unique URI
  • Use of standard HTTP methods (GET, POST; PUT, DELETE) to interact with the resource.
  • Representation-oriented: you interact with services using representations of that service. A resource referenced by one URI can have different formats, as different platforms need different formats. (e.g. browsers need HTML, JavaScript needs JSON and so on).
  • Communication between the client and the endpoint is stateless. All the associated state required by the server is passed by the client in each invocation.

In this tutorial we will show some examples of REST Services using RESTEasy.

What is RESTEasy? RESTEasy is an implementation of Jakarta RESTful Web Services API. It is essentially a frameworks to help you build RESTful Web Services and it is bundled in WildFly / JBoss EAP application server.

Create your first RESTful service

We will show how to create your first REST Service and then we will deploy it on a Jakarta EE server such as WildFly application server.

Firstly, create a Web application project using any tool. For example, using Maven:

mvn archetype:generate -DgroupId=com.mastertheboss -DartifactId=rest-demo -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

Next, include the required dependencies in your pom.xml file:

<dependencies>
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>8.0.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>jakarta.xml.bind</groupId>
        <artifactId>jakarta.xml.bind-api</artifactId>
        <version>2.3.3</version>
    </dependency>
</dependencies>

The jakartaee-api is an umbrella dependency for all Jakarta EE API (including JAX-RS). We also need to incude jakarta.xml.bind-api if we want to marshall/unmarshall XML elements.

In the first example, we will recall a RESTful Service which returns a String when a certain path is requested with an HTTP GET:

package com.sample;

import javax.ws.rs.*;

@Path("tutorial")
public class HelloWorld
{

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

The @Path annotation at class level is used to specify the base URL of the Web service. Then you can assign a @Path annotation at method level to specify the single action you want to invoke.

Before deploying your application, you need a JAX-RS class activator, which is a class extending javax.ws.rs.core.Application and declaring the Path where JAX-RS Services will be available:

package com.sample.activator;

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

@ApplicationPath("/rest")
public class JaxRsActivator extends Application {

}

In this example, if you need to invoke the helloworld service, you have to use the following URL: http://localhost:8080/rest/rest-demo/tutorial/helloworld (we suppose that the web application is named rest-demo.war)

This would produce the classic “Hello World” message on your browser. Now let’s add a parameter to our service:

@GET
@Path("helloname/{name}")
public String hello(@PathParam("name") final String name) {
  return "Hello " +name;
}

Now, we have defined the “helloname” service which accepts the {name} parameter. Now the service returns the value which is appended in the URL. Example:
http://localhost:8080/rest/rest-demo/tutorial/helloname/francesco

would return:

Hello Francesco

Returning XML from your JAX-RS services

As required by the specification, JAX-RS includes support for (un)marshalling JAXB annotated classes.
As a matter of fact, the JAX-RS server will select a different provider based on the return type or parameter type used in the resource. A JAXB Provider is selected by RESTEasy when a parameter or return type is an object that is annotated with JAXB annotations (such as @XmlRootEntity or @XmlType).
For example, supposing you were to return an XML containing the Item class:

package com.sample;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Item {
	public Item() { 	}

	public Item(String description, int price) { 
            this.description = description;
            this.price = price;
	}
 
	private String description;
	private int price;

        // Getter- Setter methods

}

Now add this method to your HelloWorld service:

@GET
@Path("item")
@Produces({"application/xml"})
public Item  getItem() {
  Item item = new Item("computer",2500);
 return item;
}

When invoked, the service (http://localhost:8080/rest-demo/rest/tutorial/item)  will return the following XML:

<item>
   <description>computer</description>
   <price>2500</price>
</item>

If you need to return an array of Item, you can simply use:

@GET
@Path("itemArray")
@Produces({"application/xml"})
public Item[]  getItem() {
  Item item[] = new Item[2];
  item[0] = new Item("computer",2500);
  item[1] = new Item("chair",100);

  return item;
}  

using curl to test the above method:

curl -s http://localhost:8080/rest-demo/rest/tutorial/itemArray | xmllint --format -

The following XML will be returned:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<collection>
  <item>
    <description>computer</description>
    <price>2500</price>
  </item>
  <item>
    <description>chair</description>
    <price>100</price>
  </item>
</collection>

If, on the othen hand, you need to use standard Java collections, you can use a java.util.List to return a set of Item objects:

@GET
@Path("itemList")
@Produces(MediaType.APPLICATION_XML)
public List<Item> getCollItems() {
	List list = new ArrayList();
	Item item1 = new Item("computer",2500);
	Item item2 = new Item("chair",100);
	Item item3 = new Item("table",200);

	list.add(item1);
	list.add(item2);
	list.add(item3);

	return list;
}

using curl to return the List of Item objects:

curl -s http://localhost:8080/rest-demo/rest/tutorial/itemList | xmllint --format -

The following XML will be returned:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<collection>
  <item>
    <description>computer</description>
    <price>2500</price>
  </item>
  <item>
    <description>chair</description>
    <price>100</price>
  </item>
  <item>
    <description>table</description>
    <price>200</price>
  </item>
</collection>

Returning JSON from your JAX-RS services

The JSON provider is widely used as it can return Collections such as lists, sets, or arrays For example:

For example, the following getJSONItems returns data using JSON provider:

@GET
@Path("itemListJson")
@Produces("application/json")
public List<Item>  getJSONItems() {
        ArrayList list = new ArrayList();
        Item item1 = new Item("computer",2500);
        Item item2 = new Item("chair",100);
        Item item3 = new Item("table",200);

        list.add(item1);
        list.add(item2);
        list.add(item3);

        return list;
}

You can use curl to request the above method:

curl -s http://localhost:8080/rest-demo/rest/tutorial/itemListJson | jq

Here is the expected outcome:

[
  {
    "description": "computer",
    "price": 2500
  },
  {
    "description": "chair",
    "price": 100
  },
  {
    "description": "table",
    "price": 200
  }
]

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/basic

Continue Learning REST Services

In this tutorial we have covered the basics of REST Services using just the HTTP GET verb. If you want to see a complete example of REST Service CRUD application (backed by JPA), then check this tutorial: 

REST CRUD application with JPA | A complete example

On the other hand, if you want to learn how to manage HTTP Parameters, we recommend this article:

RESTEasy web parameters handling

JAX-RS using older JBoss AS versions

Some installation steps might be required if you are running an older version of the application server, let’s see them in detail.

JBoss 6/7

If you are going to run RESTEasy with JBoss 6/7 you don’t have to download/install anything. The RESTEasy libraries are already bundled in the application server. Also the application server detects automatically the resources which are exported as Restful services.

All you have to do is inserting the correct web.xml namespaces:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
     version="3.0">

Older JBoss releases

If you have got any other JBoss AS release you have to perform a few basic steps for installing RESTEasy:

Step 1# Download Resteasy from the Repository

The first step is to download the latest Resteasy stable release from:
http://sourceforge.net/projects/resteasy/files/Resteasy%20JAX-RS/

Step 2# Add the following libraries to your Web application:

Step 3# Define listeners and bootstrap classes:

Add the following web.xml configuration:

<web-app>
    <display-name>RestEasy sample Web Application</display-name>

    <listener>
        <listener-class>
            org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>Resteasy</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>sample.HelloWorldApplication</param-value>
        </init-param>

    </servlet>

    <servlet-mapping>
        <servlet-name>Resteasy</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

The org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap class is a ServletContextListener that configures an instance of an ResteasyProviderFactory and Registry.

Then, you need to set the parameter javax.ws.rs.core.Application with the singleton class which is used by your application to enlist all JAX-RS root resources and providers. Using JBoss 6-M4 and Higher this is not necessary as it’s performed automatically by the application server when you deploy the .war

Finally you need to specify which url patterns need to be filtered by Resteasy. With the /* url-pattern all resources will be passed to resteasy servlet.

Step 4# Create the Singleton class

Now we need to add a special Singleton class to enlist all JAX-RS resources, as indicated by the javax.ws.rs.Application parameter.

package sample;

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;

import sample.HelloWorld;

public class HelloWorldApplication extends Application
{
    private Set<Object> singletons = new HashSet();
    private Set<Class<?>> empty = new HashSet();

    public HelloWorldApplication() {
        // ADD YOUR RESTFUL RESOURCES HERE
        this.singletons.add(new HelloWorld());
    }

    public Set<Class<?>> getClasses()
    {
        return this.empty;
    }

    public Set<Object> getSingletons()
    {
        return this.singletons;
    }
}

This is your Application class which will enlist your RESTful services in the constructor. In our example, we will add the HelloWorld service.

Autoscanning of resources

If you prefer, you can let RESTeasy scan for your resources instead of adding the SingletonBean which installs your JAX-WS resources.  Just add the following configuration in your web.xml:

   <context-param>
      <param-name>resteasy.scan</param-name>
      <param-value>true</param-value>
   </context-param>
   <context-param>
      <param-name>resteasy.servlet.mapping.prefix</param-name>
      <param-value>/</param-value>
   </context-param>

REST CRUD application with JPA | A complete example

This tutorial will teach you how to create an example REST (JAX-RS) CRUD application (to be run on the top of WildFly application server) which uses JPA to store your Resources.

Building the CRUD JAX-RS Service

Our JAX-RS Service will use the following HTTP methods to send and retrieve data from/to the server:

  • GET: The GET method is used to retrieve data from the server. This is a read-only method,
  • POST: This  method sends data to the server and creates a new resource. The resource it creates is subordinate to some other parent resource.
  • PUT:  This method is most often used to update an existing resource.
  • DELETE: This method is used to delete a resource specified by its URI.

Firstly, let’s create a Web project using a Maven archetype:

mvn archetype:generate -DgroupId=com.mastertheboss.jaxrs -DartifactId=jaxrs-DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

Then, we will add the Model object named Customer:

@Entity
@NamedQuery(name = "Customers.findAll",
        query = "SELECT c FROM Customer c ORDER BY c.id")
public class Customer {
    @Id
    @SequenceGenerator(
            name = "customerSequence",
            sequenceName = "customerId_seq",
            allocationSize = 1,
            initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence")
    private Long id;
    @Column(length = 40)
    private String name;
    @Column(length = 40)
    private String surname;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

}

The interaction with the Customer object is done through a CustomerRepository Class which follows here:

import javax.enterprise.context.ApplicationScoped;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;


@ApplicationScoped
public class CustomerRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Customer> findAll() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList();
    }

    public Customer findCustomerById(Long id) {

        Customer customer = entityManager.find(Customer.class, id);

        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }
    @Transactional
    public void updateCustomer(Long id, String name, String surname) {

        Customer customerToUpdate = findCustomerById(id);
        customerToUpdate.setName(name);
        customerToUpdate.setSurname(surname);
    }
    @Transactional
    public void createCustomer(Customer customer) {

        entityManager.persist(customer);

    }
    @Transactional
    public void deleteCustomer(Long customerId) {

        Customer c = findCustomerById(customerId);
        entityManager.remove(c);
    }


}

It is worth notice that, since the Repository is a CDI bean and not an EJB (which is by default a Transactional component), we need to specify the @Transactional annotation on the top of methods that execute a Transaction. Next on the list is the CustomerEndpoint class, which exposes to the Front-End a set of GET/POST/PUT and DELETE HTTP methods:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;

@Path("customers")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @Inject CustomerRepository customerRepository;

    @GET
    public List<Customer> getAll() {
        return customerRepository.findAll();
    }

    @POST
    public Response create(Customer customer) {
        customerRepository.createCustomer(customer);
        return Response.status(201).build();
    }

    @PUT
    public Response update(Customer customer) {
        customerRepository.updateCustomer(customer);
        return Response.status(204).build();
    }
    @DELETE
    public Response delete(@QueryParam("id") Long customerId) {
        customerRepository.deleteCustomer(customerId);
        return Response.status(204).build();
    }

}

The last Class we need to add is a JaxRsActivator which sets the Web context for the REST Service, activating it as well:

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


@ApplicationPath("/rest")
public class JaxRsActivator extends Application {
   /* class body intentionally left blank */
}

Our example application will run against the default H2 Database which is provided in WildFly, so our persistence.xml will reference the ExampleDS Datasource for this purpose:

<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/persistence
        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="primary">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <!-- Properties for Hibernate -->
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>

Within the folder resources we can add as well an import.sql script to have some Customer objects already available:

INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Homer','Simpson');
INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Bart','Simpson');

In order to build our projects, we can use the super-lean Jakarta EE dependency, which follows here:

<dependencies>
    <dependency>
      <groupId>jakarta.platform</groupId>
      <artifactId>jakarta.jakartaee-api</artifactId>
      <version>8.0.0</version>
      <scope>provided</scope>
    </dependency>
</dependencies>

Here is the Project tree:

src
└── main
    ├── java
    │   └── com
    │       └── mastertheboss
    │           └── jaxrs
    │               ├── CustomerEndpoint.java
    │               ├── CustomerException.java
    │               ├── Customer.java
    │               ├── CustomerRepository.java
    │               └── JaxRsActivator.java
    ├── resources
    │   ├── import.sql
    │   └── META-INF
    │       └── persistence.xml
    └── webapp
        ├── index.html
        └── WEB-INF
            └── beans.xml

Running the project

Start WildFly application server using any available configuration:

$ ./standalone.sh

Then, as our project includes the WildFly Maven plugin (check the source code below), we can simply run it as follows:

mvn install wildfly:deploy

Let’s test the GET method:

curl -s http://localhost:8080/jaxrs-demo/rest/customers | jq
[
  {
    "id": 1,
    "name": "Homer",
    "surname": "Simpson"
  },
  {
    "id": 2,
    "name": "Bart",
    "surname": "Simpson"
  }
]

Next, let’s add one more Customer:

curl -d '{"name":"John", "surname":"Smith"}' -H "Content-Type: application/json" -X POST http://localhost:8080/jaxrs-demo/rest/customers

To verify that the Customer has been added:

curl -s http://localhost:8080/jaxrs-demo/rest/customers | jq
[
  {
    "id": 1,
    "name": "Homer",
    "surname": "Simpson"
  },
  {
    "id": 2,
    "name": "Bart",
    "surname": "Simpson"
  },
  {
    "id": 3,
    "name": "John",
    "surname": "Smith"
  }
]

Next, we will modify the Customer with id “3”:

curl -d '{"id":"3", "name":"Will", "surname":"Smith"}' -H "Content-Type: application/json" -X PUT http://localhost:8080/jaxrs-demo/rest/customers

Finally, to delete the Customer with id “3”:

curl  -H "Content-Type: application/json" -X DELETE http://localhost:8080/jaxrs-demo/rest/customers?id=3

Conclusion

We have learned how to create a JAX-RS application to be deployed on a Jakarta EE application server.

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/crud

Configuring RESTEasy Applications

Since WildFly 20, you can configure RESTEasy through the MicroProfile Config project (https://github.com/eclipse/microprofile-config). The use of MicroProfile Config offers to REST developers a plenty of flexibility in controlling runtime configuration.

If you want to read more details about MicroProfile Config API, we recommend checking this tutorial: Configuring Microservices with MicroProfile Configuration

In a nutshell, the MicroProfile Config, defines a ConfigSource as Map<String, String> of property names to values. In turn, the ConfigSource represents a sequence of ConfigSources, ordered by priority. The priority of a ConfigSource is given by an ordinal (represented by an int), with a higher value indicating a higher priority. Here is the ordered list (Top-Down Ranking) ConfigSources:

  • a ConfigSource based on System.getProperties() (ordinal = 400).
  • a ConfigSource based on System.getenv() (ordinal = 300)
  • a ConfigSource for each META-INF/microprofile-config.properties file on the ClassPath, separately configurable via a config_ordinal property inside each file (default ordinal = 100)

Using MicroProfile Config with REST Easy

Before WildFly 20, configuration properties were added at application level through the standard web.xml descriptor. For example, to set the resteasy.role.based.security property:

<web-app>
...
   <context-param>
      <param-name>resteasy.role.based.security</param-name>
      <param-value>true</param-value>
   </context-param>
</web-app>

Now, you can define this property with any of the ConfigSources, for example through the META-INF/microprofile-config.properties file:

resteasy.role.based.security=true

You can check an example application which uses RESTEasy Role Base Security here: Securing JAX-RS Services in WildFly applications

The full list of Properties you can set for your REST Easy Application is listed in this Table:

Configuration Param Default Description
resteasy.servlet.mapping.prefix NA If the url-pattern for the RESTEasy servlet-mapping is not /*
resteasy.providers NA A comma delimited list of fully qualified @Provider class names you want to register
resteasy.use.builtin.providers true Whether or not to register default, built-in @Provider classes
resteasy.resources NA A comma delimited list of fully qualified JAX-RS resource class names you want to register
resteasy.jndi.resources NA A comma delimited list of JNDI names which reference objects you want to register as JAX-RS resources
javax.ws.rs.Application NA Fully qualified name of Application class to bootstrap in a spec portable way
resteasy.media.type.mappings NA Replaces the need for an Accept header by mapping file name extensions (like .xml or .txt) to a media type. Used when the client is unable to use an Accept header to choose a representation (i.e. a browser).
resteasy.language.mappings NA Replaces the need for an Accept-Language header by mapping file name extensions (like .en or .fr) to a language. Used when the client is unable to use an Accept-Language header to choose a language (i.e. a browser).
resteasy.media.type.param.mapping NA Names a query parameter that can be set to an acceptable media type, enabling content negotiation without an Accept header.
resteasy.role.based.security false Enables role based security.
resteasy.document.expand.entity.references false Expand external entities in org.w3c.dom.Document documents and JAXB object representations
resteasy.document.secure.processing.feature true Impose security constraints in processing org.w3c.dom.Document documents and JAXB object representations
resteasy.document.secure.disableDTDs true Prohibit DTDs in org.w3c.dom.Document documents and JAXB object representations
resteasy.wider.request.matching false Turns off the JAX-RS spec defined class-level expression filtering and instead tries to match version every method’s full path.
resteasy.use.container.form.params false Obtain form parameters by using HttpServletRequest.getParameterMap(). Use this switch if you are calling this method within a servlet filter or eating the input stream within the filter.
resteasy.rfc7232preconditions false Enables RFC7232 compliant HTTP preconditions handling.
resteasy.gzip.max.input 10000000 Imposes maximum size on decompressed gzipped .
resteasy.secure.random.max.use 100 The number of times a SecureRandom can be used before reseeding.
resteasy.buffer.exception.entity true Upon receiving an exception, the client side buffers any response entity before closing the connection.
resteasy.add.charset true If a resource method returns a text/* or application/xml* media type without an explicit charset, RESTEasy will add “charset=UTF-8” to the returned Content-Type header. Note that the charset defaults to UTF-8 in this case, independent of the setting of this parameter.
resteasy.disable.html.sanitizer false Normally, a response with media type “text/html” and a status of 400 will be processed so that the characters “/”, “<“, “>”, “&”, “”” (double quote), and “‘” (single quote) are escaped to prevent an XSS attack. If this parameter is set to “true”, escaping will not occur.
resteasy.patchfilter.disabled false Turns off the default patch filter to handle JSON patch and JSON Merge Patch request. A customerized patch method filter can be provided to serve the JSON patch and JSON merge patch request instead.

JAX-RS CRUD Application using Vue.js and RESTEasy

Let’s check in this tutorial how to build a  JAX-RS CRUD application (on the top of WildFly application server) using Vue.js and Axios as Front End.

Let’s start with some basic concepts about Vue.js: in a nutshell, Vue.js is a system that enables us to declaratively render data to the DOM using straightforward template syntax. Example:

<div id="app">
  {{ message }}
</div>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  }
})

Assumed that you have imported the Javascript library for Vue:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Then, the above code will print in the div element an “Hello world” message.

Vue, however, doesn’t have a built-in HTTP request library. The recommended approach is to use Axios to interact with REST APIs. Axios is a promise-based HTTP client for making Ajax requests, and will work great for our purposes. It provides a simple and rich API that can easily wrap a basic CRUD REST Service.

Let’s build our example, starting from the REST Service.

Building the CRUD JAX-RS Service

Our JAX-RS Service will let us Create, Read, Update and Delete a list of Customer objects. So let’s add the Model object Customer to a standard Java EE project:

@Entity
@NamedQuery(name = "Customers.findAll",
        query = "SELECT c FROM Customer c ORDER BY c.id")
public class Customer {
    @Id
    @SequenceGenerator(
            name = "customerSequence",
            sequenceName = "customerId_seq",
            allocationSize = 1,
            initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence")
    private Long id;
    @Column(length = 40)
    private String name;
    @Column(length = 40)
    private String surname;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

}

The interaction with the Customer object is done through a CustomerRepository Class which follows here:

import javax.enterprise.context.ApplicationScoped;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;


@ApplicationScoped
public class CustomerRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Customer> findAll() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList();
    }

    public Customer findCustomerById(Long id) {

        Customer customer = entityManager.find(Customer.class, id);

        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }
    @Transactional
    public void updateCustomer(Long id, String name, String surname) {

        Customer customerToUpdate = findCustomerById(id);
        customerToUpdate.setName(name);
        customerToUpdate.setSurname(surname);
    }
    @Transactional
    public void createCustomer(Customer customer) {

        entityManager.persist(customer);

    }
    @Transactional
    public void deleteCustomer(Long customerId) {

        Customer c = findCustomerById(customerId);
        entityManager.remove(c);
    }


}

It is worth notice that, since the Repository is a CDI bean and not an EJB (which is by default a Transactional component), we need to specify the @Transactional annotation on the top of methods that execute a Transaction. Next on the list is the CustomerEndpoint class, which exposes to the Front-End a set of GET/POST/PUT and DELETE HTTP methods:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;


@Path("customers")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @Inject CustomerRepository customerRepository;

    @GET
    public List<Customer> getAll() {
        return customerRepository.findAll();
    }

    @POST
    public Response create(Customer customer) {
        customerRepository.createCustomer(customer);
        return Response.status(201).build();
    }

    @PUT
    public Response update(@QueryParam("id") Long id, @QueryParam("name") String name, @QueryParam("surname") String surname) {
        customerRepository.updateCustomer(id,name,surname);
        return Response.status(204).build();
    }
    @DELETE
    public Response delete(@QueryParam("id") Long customerId) {
        customerRepository.deleteCustomer(customerId);
        return Response.status(204).build();
    }

}

The last Class we need to add is a JaxRsActivator which sets the Web context for the REST Service, activating it as well:

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


@ApplicationPath("/rest")
public class JaxRsActivator extends Application {
   /* class body intentionally left blank */
}

Our example application will run against the default H2 Database which is provided in WildFly, so our persistence.xml will reference the ExampleDS Datasource for this purpose:

<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/persistence
        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="primary">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <!-- Properties for Hibernate -->
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>

Within the folder resources we can add as well an import.sql script to have some Customer objects already available:

INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Homer','Simpson');
INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Bart','Simpson');

In order to build our projects, we can use the super-lean Jakarta EE dependency, which follows here:

<dependencies>
    <dependency>
      <groupId>jakarta.platform</groupId>
      <artifactId>jakarta.jakartaee-api</artifactId>
      <version>8.0.0</version>
      <scope>provided</scope>
    </dependency>
</dependencies>

Finally, in order to force the Web context to be independent from the artifactId, we have added a jboss-web.xml which dictates the Web context to be “vuejs-demo”:

<jboss-web>  
    <context-root>vuejs-demo</context-root>  
</jboss-web>  

Here is the Project tree:

src
└── main
    ├── java
    │   └── com
    │       └── mastertheboss
    │           └── jaxrs
    │               ├── CustomerEndpoint.java
    │               ├── CustomerException.java
    │               ├── Customer.java
    │               ├── CustomerRepository.java
    │               └── JaxRsActivator.java
    ├── resources
    │   ├── import.sql
    │   └── META-INF
    │       └── persistence.xml
    └── webapp
        ├── index.html
        └── WEB-INF
            ├── beans.xml
            └── jboss-web.xml

Coding the Vue.js Front-End

Now let’s code the Vue.js layer. For this purpose, we have added a single index.html page in our project. To get started, we obviously need to import the required libraries:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>

And now for the rest of the page:

<body>
<h3>JAX-RS Demo (WildFly) with VueJS</h3>
<div id="app">

    <table class="blueTable">
        <thead >
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Surname</th>
            <th>Delete</th>
            <th>Update</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="user in users" :key="user.id">
            <td>{{user.id}}</td>
            <td>{{user.name}}</td>
            <td>{{user.surname}}</td>
            <td>  <button class="button"  @click="deleteUser(user.id)">Delete</button></td>
            <td>  <button class="button"  @click="updateUser(user.id)">Update</button></td>
        </tr>
        </tbody>
    </table>
    <br/>
    <div>
        <form name="form" >
            <input type="text" placeholder="Name" v-model="name" size="60"/><br/>
            <input type="text" placeholder="Surname" v-model="surname" size="60"/><br/>
            <input type="submit" value="Save" @click="createUser"/><br/>
        </form>
    </div>
</div>


<script>


new Vue({
    el: "#app",
    data() {
        return {
            users: []
        }
    },
    methods: {


        updateUser: function(id) {

                axios.put("/vuejs-demo/rest/customers?id="+id+"&name="+this.name+"&surname="+this.surname)
                .then(response => {
                    this.listUsers()
                })
                this.name='';
                this.surname='';
        },

        deleteUser: function(id) {

                axios.delete("/vuejs-demo/rest/customers?id="+id)
                .then(response => {
                    this.listUsers()
                })
        },

        createUser: function() {

          var user = {
                // name and surname should exist in [data] or [computed] section
                name: this.name,
                surname: this.surname
              };


            axios.post("/vuejs-demo/rest/customers",  user)
                .then(response => {
                    this.listUsers()
                })


        },
        listUsers: function() {
            axios.get("/vuejs-demo/rest/customers", {

                })
                .then(response => {
                    this.users = response.data
                })
        }
    },
    mounted: function() {

       this.listUsers()
    }


})
</script>

The above code should be pretty intuitive. We have bootstrapped Vue.js definind three things:

  • A variable of type array called data which contains the list of Customer objects
  • A set of methods, to manage HTTP CRUD actions
  • We have mounted a function that fires up when the page loads. This function simply loads the listUsers method to fill our Table
That's all!

Running the project

Start WildFly application server using any available configuration:

standalone.sh

Then, as our project includes the WildFly Maven plugin (check the source code below), we can simply run it as follows:

mvn install wildfly:deploy

Here is our simple JAX-RS CRUD Example in action:

By entering Name and Surname in the text field and clicking on Save a new Customer will be added. On the other hand, if you click on Update, the corresponding customer will be updated with the content of your text field. Obviosuly Delete evicts the Customer on that row.

In the next tutorial, we will show how to transform our Java EE application in a Quarkus application in a matter of minutes! Stay tuned!

You can check the source code for this tutorial here:

https://github.com/fmarchioni/mastertheboss/tree/master/web/vuejs-demo

JAX-RS Cheatsheet

Here is a comprehensive JAX-RS Cheatsheet to help you code your REST Services.

JAX RS-Annotations

@Path: Path to annotate a method or a Class with the resource path it represents.

@Path("/hello")
public class CustomerEndpoint {
}

@Produces: To specify the output type that the resource produces, or in a narrower scope the type of output that a method in a resource

produces.

@Produces(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_JSON)

@Consumes: To specify the type of input that the resource consumes, or in a narrower scope the type of input that a method in a resource consumes.

@Consumes(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_JSON)

@ApplicationPath: Identifies the application path that serves as the base URI for all resource URIs provided by path.

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
 
@ApplicationPath("/rest")
public class RestActivator extends Application {
     
}

@Context: Can be used to inject contextual objects such as UriInfo, which provides contextual request-specific information about the request URI.

@Path("/")

public class DemoResource {

    private final @Context HttpHeaders httpHeaders;
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getHeaders(){
        // Code here that uses httpHeaders
    }
}

HTTP Operations: JAX-RS defines five annotations that map to specific HTTP operations:

  • @javax.ws.rs.GET: To map an HTTP GET Request
  • @javax.ws.rs.PUT: To map an HTTP PUT Request
  • @javax.ws.rs.POST: To map an HTTP POST Request
  • @javax.ws.rs.DELETE: To map an HTTP DELETE Request
  • @javax.ws.rs.HEAD: To map an HTTP HEAD Request

JAX RS Parameters

@QueryParam

public Response getParams(
    @QueryParam("s") @DefaultValue("") String myStr,
    @QueryParam("i") @DefaultValue("-1") int myInt) {
  String s = "s=" + myStr + ", i=" + myInt;
  return Response.ok(s).build();
}
$ curl 'http://localhost:8080/queryParam?s=Hi&i=123'

@MatrixParam

@GET
@Path("matrixParam")
public Response getMatrixParam(
    @MatrixParam("height") int height,
    @MatrixParam("width") int width) {
  return Response.ok("height=" + height + ", width=" + width).build();
}
$ curl http://localhost:8080/matrixParam;height=1;width=2

@PathParam

@GET
@Path("pathParam/{p}")
public Response getParams(@PathParam("p") String v) {
  return Response.ok(v).build();
}
$ curl http://localhost:8080/pathParam/foo

@HeaderParam

@GET
@Path("headerParam")
public Response getHeaderParam(@HeaderParam("p") String v) {
  return Response.ok(v).build();
}
$ curl -v -H 'p: foo' http://localhost:8080/headerParam

@CookieParam

@GET
@Path("cookieParam")
public Response getCookieParam(@CookieParam("p") String v) {
  return Response.ok(v).build();
}
$ curl -v -H 'Cookie: p=foo' http://localhost:8080/cookieParam

@FormParam

@POST
@Path("formParam")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response postFormParam(@FormParam("p") String v) {
  return Response.ok(v).build();
}
$ curl -v -d 'p=foo' http://localhost:8080/formParam

@BeanParam

@POST
@Path("beanParam")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response postBeanParam(@BeanParam Image image) {
  String s = "height=" + image.getHeight();
  s += ", width=" + image.getWidth();
  return Response.ok(s).build();
}

import javax.ws.rs.FormParam;

public class Image {

  @FormParam("height")
  private int height;

  @FormParam("width")
  private int width;

  public int getHeight() {
    return height;
  }

  public int getWidth() {
    return width;
  }
}
$ curl -d 'height=1' -d 'width=2' http://localhost:8080/beanParam 

How to produce a Response

Response with status:

String message = "This is a text response";

return Response
  .status(Response.Status.OK)
  .entity(message)
  .build();

Response in custom format

String json = //convert entity to json
return Response.ok(json, MediaType.APPLICATION_JSON).build();

Throwing Exception

@GET
@Path("{id}")
@Produces("application/xml")
public Customer getCustomer(@PathParam("id") int id) {

   Customer cust = findCustomer(id);
   if (cust == null) {
     throw new WebApplicationException(Response.Status.NOT_FOUND);
   }
   return cust;
}

JAX WS Client API

Client client = ClientBuilder.newClient();
SimpleProperty p1 = new SimpleProperty("mykey","value");
WebTarget myResource = client.target(BASE_URL+"/param/add");

Response rs = myResource.request(MediaType.APPLICATION_XML)
        .post(Entity.xml(p1), Response.class);

assertEquals(rs.getStatus(),200);
String out = rs.readEntity(String.class);
assertEquals(out,"Success");

Async API

Sample Async method:

@GET
@Path("/xmlasync/{id}")
@Produces(MediaType.APPLICATION_XML)
public void asyncGet(final @Suspended AsyncResponse asyncResp,
	final @PathParam("id") int id) {
		asyncResp.setTimeout(10, TimeUnit.SECONDS);
		asyncResp.setTimeoutHandler(new MyTimeoutHandler());
		new Thread(new Runnable() {
			public void run() {
				SimpleProperty p = ejb.getList().get(id);
				asyncResp.resume(p);
			}
	}).start();
}

public class MyTimeoutHandler implements TimeoutHandler {
	public void handleTimeout(AsyncResponse asyncResp) {
		Response r = Response.serverError().status( HttpURLConnection.
		HTTP_UNAVAILABLE).build( );
		asyncResp.resume( r );
	}
}

Filters: Used to modify Request or Response Headers

Server side filters: Decorate Request:

import javax.ws.rs.container.ContainerRequestFilter;
import javax.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);
   }
}

Server side filters: Decorate Response:

import javax.ws.rs.container.ContainerRequestFilter;
import javax.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);
   }
}

Client side filters: Decorate Request:

import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientRequestContext;

public class ClientCacheRequestFilter implements ClientRequestFilter {
   private Cache cache;

   public ClientCacheRequestFilter(Cache cache) {
      this.cache = cache;
   }

   public void filter(ClientRequestContext ctx) throws IOException {
      if (!ctx.getMethod().equalsIgnoreCase("GET")) return;

      CacheEntry entry = cache.getEntry(request.getUri());
      if (entry == null) return;

      if (!entry.isExpired()) {
         ByteArrayInputStream is = new ByteArrayInputStream(entry.getContent());
         Response response = Response.ok(is)
                                     .type(entry.getContentType()).build();
         ctx.abortWith(response);
         return;
      }

      String etag = entry.getETagHeader();
      String lastModified = entry.getLastModified();

      if (etag != null) {
         ctx.getHeaders.putSingle("If-None-Match", etag);
      }

      if (lastModified != null) {
         ctx.getHeaders.putSingle("If-Modified-Since", lastModified);
      }
   }

}

Client side filters: Decorate Response:

public class CacheResponseFilter implements ClientResponseFilter {
   private Cache cache;

   public CacheResponseFilter(Cache cache) {
      this.cache = cache;
   }

   public void filter(ClientRequestContext request,
                      ClientResponseContext response)
            throws IOException {
      if (!request.getMethod().equalsIgnoreCase("GET")) return;

      if (response.getStatus() == 200) {
         cache.cacheResponse(response, request.getUri());
      } else if (response.getStatus() == 304) {
         CacheEntry entry = cache.getEntry(request.getUri());
         entry.updateCacheHeaders(response);
         response.getHeaders().clear();
         response.setStatus(200);
         response.getHeaders().putSingle("Content-Type", entry.getContentType());
         ByteArrayInputStream is = new ByteArrayInputStream(entry.getContent());
         response.setInputStream(is);
      }
   }
}

Interceptors: Reader and Writer Interceptors are used to control the Message Body

Writer Interceptor:

@Provider
public class GZIPEncoder implements WriterInterceptor {

   public void aroundWriteTo(WriterInterceptorContext ctx)
                    throws IOException, WebApplicationException {
      GZIPOutputStream os = new GZIPOutputStream(ctx.getOutputStream());
      ctx.getHeaders().putSingle("Content-Encoding", "gzip");
      ctx.setOutputStream(os);
      ctx.proceed();
      return;
   }
}

Reader Interceptor:

@Provider
public class GZIPDecoder implements ReaderInterceptor {
   public Object aroundReadFrom(ReaderInterceptorContext ctx)
                                throws IOException {
      String encoding = ctx.getHeaders().getFirst("Content-Encoding");
      if (!"gzip".equalsIgnoreCase(encoding)) {
         return ctx.proceed();
      }
      GZipInputStream is = new GZipInputStream(ctx.getInputStream());
      ctx.setInputStream(is);
      return ctx.proceed(is);
   }
}

JAX-RS Dependency for WildFly

<dependency>
      <groupId>org.jboss.spec.javax.ws.rs</groupId>
      <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
      <scope>provided</scope>
</dependency>

JAX-RS Cheatsheet by mastertheboss.com – All rights reserved

WildFly Hello World example application

In this tutorial we will learn how to create a simple Hello World JAX-RS application for WildFly using just Maven and the Command Line.

Pre-requisites: You need a WildFly application server. Read here how to get started with it: Getting started with WildFly

Next, we will create a project using a Maven archetype to bootstrap a Web application project. You can use one simple like the following one:

mvn -DarchetypeGroupId=org.codehaus.mojo.archetypes \
-DarchetypeArtifactId=webapp-javaee7 \
-DgroupId=com.mastertheboss -DartifactId=helloworld \
-Dversion=1.1 -Dpackage=com.mastertheboss \
-Darchetype.interactive=false --batch-mode --update-snapshots \
archetype:generate

Now, let’s configure the project dependency file (pom.xml) so that we can include REST Endpoints in it:

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mastertheboss</groupId>
  <artifactId>helloworld</artifactId>
  <packaging>war</packaging>
  <version>1.0.0</version>
  <name>Demo REST Service</name>
  <url>http://www.mastertheboss.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <version.server.bom>17.0.0.Final</version.server.bom>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.wildfly.bom</groupId>
        <artifactId>wildfly-javaee8-with-tools</artifactId>
        <version>${version.server.bom}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.jboss.spec.javax.ws.rs</groupId>
      <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.enterprise</groupId>
      <artifactId>cdi-api</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>

        </configuration>
      </plugin>
      <plugin>
        <groupId>org.wildfly.plugins</groupId>
        <artifactId>wildfly-maven-plugin</artifactId>
        <version>2.0.0.Final</version>
      </plugin>
    </plugins>
  </build>
</project>

Are you using Gradle to build your project? No worries, check out this tutorial: Gradle tutorial for WildFly users

Then let’s build the Hello World example. We basically need three items:

A simple Model Class:

package com.mastertheboss.jaxrs.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class SimpleProperty {
	public SimpleProperty() {

	}
 
	private String key;
	private String value;

	public SimpleProperty(String key, String value) {
		super();
		this.key = key;
		this.value = value;
	}
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}

	public String getKey() {
		return key;
	}
	public void setKey(String key) {
		this.key = key;
	}
}

The we need a REST Endpoint:

package com.mastertheboss.jaxrs.service;

import com.mastertheboss.jaxrs.model.SimpleProperty;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;


@Path("/hello")
public class HelloService {
	@GET
	@Path("/text")
	public String getHello () 
	{
		return "hello world!";
	} 
	@GET
	@Path("/json")
	@Produces(MediaType.APPLICATION_JSON)
	public SimpleProperty getPropertyJSON ()
	{
        SimpleProperty p = new SimpleProperty("key","value");
		return p;
	}
	@GET
	@Path("/xml")
	@Produces(MediaType.APPLICATION_XML)
	public SimpleProperty getPropertyXML () 
	{
        SimpleProperty p = new SimpleProperty("key","value");
		return p;
	}
}

And finally, a REST Activator class, that will expose our services under the /rest Path:

package com.mastertheboss.jaxrs.activator;

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


@ApplicationPath("/rest")
public class JaxRsActivator extends Application {
   
}

That’s all. As we have included the Maven WildFly plugin in the project, let’s build and deploy it with:

$ mvn install wildfly:deploy

Now we can test all available endpoints. The first one will just return a text String:

$ curl http://localhost:8080/helloworld/rest/hello/text
hello world!

The second endpoint produces a JSON output:

$ curl http://localhost:8080/helloworld/rest/hello/json
{"key":"key","value":"value"}

And finally, the last one produces an XML output:

$ curl http://localhost:8080/helloworld/rest/hello/xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><simpleProperty><key>key</key><value>value</value></simpleProperty>

That’s all! Congratulations, you have just mastered the Hello World WildFly tutorial

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/javaee/helloworld

REST Services cURL cheatsheet

Here is a cheatsheet that might be useful for REST developers that need a simple tool like curl for testing their REST services.

How to execute a POST application/x-www-form-urlencoded

As application/x-www-form-urlencoded is the default, you can just execute:

$ curl -d "param1=value1&param2=value2" -X POST http://localhost:8080/service

If you want to use explicit setting:

$ curl -d "param1=value1&param2=value2" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://localhost:8080/service

How to send parameters contained in a file

curl -d "@data.txt" -X POST http://localhost:8080/service

Where data.txt might contain, for example:

param1=value1&param2=value2

How to execute a POST application/json

curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST http://localhost:8080/service

How to execute a POST application/json from a file:

curl -d "@data.json" -X POST http://localhost:8080/service

How to execute a POST request with form parameters:

$ curl -X PUT -d 'param1=value1&param2=value2' http://localhost:8080/service

How to execute a POST request with Json parameters:

$ curl -X PUT -H "Content-Type: application/json" -d '{"key1":"value1", "key2":"value2"}' http://localhost:8080/service

How to execute an HEAD request:

curl -I http://localhost:8080/service

How to execute a DELETE request:

curl -X "DELETE" http://www.url.com/page

How to execute a non-proxy request:

curl --noproxy 127.0.0.1 http://localhost:8080/service

How to execute a GET with extra Headers:

For example, let’s set json as Content type:

curl -i -H "Accept: application/json" -H "Content-Type: application/json" http://localhost:8080/service

Or let’s set XML as Content type:

curl -H "Accept: application/xml" -H "Content-Type: application/xml" -X GET http://localhost:8080/service

How to execute a GET with credentials:

curl -u $username:$password http://localhost:8080/service

Consuming a RESTful service with Ajax

In this tutorial you will consume a RESTful service (that we deploy on WildFly application server) with a simple Ajax Client.

Let’s start from the REST Endpoint:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;
 
 
@Path("persons")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class DemoEndpoint {
 
    @Inject DemoRepository demoRepository;
 
    @GET
    public List<Person> getAll() {
        return demoRepository.findAll();
    }
 
    @POST
    public Response create(Person p) {
        demoRepository.createPerson(p);
        return Response.status(201).build();
 
    }
 
    @PUT
    public Response update(Person p) {
        demoRepository.updatePerson(p);
        return Response.status(204).build();
    }
    @DELETE
    public Response delete(@QueryParam("id") Integer id) {
        demoRepository.deletePerson(id);
        return Response.status(204).build();
    }
 
}

As you can see, this REST Service contains CRUD methods to perform the basic operations on the Person class.

The Repository class uses a plain memory structure to store your objects:

package com.packt.quarkus.chapter4;

import javax.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;


@ApplicationScoped
public class DemoRepository {

    List<Person> list = new ArrayList();
    int counter;

    public int getNextCustomerId() {
        return counter++;
    }

    public List<Person> findAll() {
        return list;
    }

    public Person findPersonById(Integer id) {
        for (Person c: list) {
            if (c.getId().equals(id))  {
                return c;
            }
        }
        throw new RuntimeException("Person not found!");
    }

    public void updatePerson(Person person) {
        Person personToUpdate = findPersonById(person.getId());
        personToUpdate.setName(person.getName());
        personToUpdate.setSurname(person.getSurname());
    }

    public void createPerson(Person person) {
        person.setId(getNextCustomerId());
        findAll().add(person);
    }

    public void deletePerson(Integer id) {
        Person c = findPersonById(id);
        findAll().remove(c);
    }
}

Then, here is the POJO Class named Person:

public class Person {
    private Integer id;
    private String name;
    private String surname;

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}

Finally, include a REST Activator which activates REST Services under the URI “/rest”:

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

@ApplicationPath("/rest")
public class JaxRsActivator extends Application {
   /* class body intentionally left blank */
}

 Done with the server side. Now let’s add an index.hml page to display the List of Person in an HTML Table:

<html>
<head>
    <!-- little bit of css to beutify the table -->
    <link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>


    <script type="text/javascript">

$(document).ready(function () {
    $.getJSON("http://localhost:8080/rest/app/persons",
    function (json) {
        var tr;
        for (var i = 0; i < json.length; i++) {
            tr = $('<tr/>');
            tr.append("<td>" + json[i].id + "</td>");
            tr.append("<td>" + json[i].name + "</td>");
            tr.append("<td>" + json[i].surname + "</td>");
            $('table').append(tr);
        }
    });
});
</script>
</head>

<body>
<div align="left" style="margin-top: 10%;">
    <fieldset style="border: none;">
        <legend><strong>Users List</strong></legend>

        <!-- table to show data -->
        <table class="greyGridTable">
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Surname</th>
            </tr>
            </thead>
            <tbody>

            </tbody>
        </table>
    </fieldset>
</div>

</body>
</html>

Some CSS style has been included in the file stylesheet.css:

table.greyGridTable {
  border: 2px solid #FFFFFF;
  width: 100%;
  text-align: center;
  border-collapse: collapse;
}
table.greyGridTable td, table.greyGridTable th {
  border: 1px solid #FFFFFF;
  padding: 3px 4px;
}
table.greyGridTable tbody td {
  font-size: 13px;
}
table.greyGridTable td:nth-child(even) {
  background: #EBEBEB;
}
table.greyGridTable thead {
  background: #FFFFFF;
  border-bottom: 4px solid #333333;
}
table.greyGridTable thead th {
  font-size: 15px;
  font-weight: bold;
  color: #333333;
  text-align: center;
  border-left: 2px solid #333333;
}
table.greyGridTable thead th:first-child {
  border-left: none;
}

table.greyGridTable tfoot td {
  font-size: 14px;
}

That’s all. Make sure the following dependencies are included in your pom.xml file:

<dependency>
      <groupId>org.jboss.spec.javax.ws.rs</groupId>
      <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
      <scope>provided</scope>
</dependency>
<dependency>
      <groupId>javax.enterprise</groupId>
      <artifactId>cdi-api</artifactId>
      <scope>provided</scope>
</dependency>

Deploy the application with

$ mvn package wildfly:deploy

Now we can try adding some data with cURL:

$ curl -d '{"name":"john", "surname":"black"}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8080/persons
$ curl -d '{"name":"victor", "surname":"frankenstein"}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8080/persons

Let’s surf to localhost:8080 to check that Data is listed in our table:

Great. We could see how easily JSON data can be parsed with some help from jQuery and Ajax!

RESTAssured tutorial

This tutorial discusses about RESTAssured, a Testing framework that is used by several frameworks (including Quarkus) to test specifically REST applications.

REST Assured is a Java API that can be used to validate RESTful services through its fluent DSL (Domain specific Languages) that describes a connection to an HTTP endpoint and expected results.

Here is a REST Assured Hello World example:

import static com.jayway.restassured.RestAssured.given;

import org.junit.Test;

public class MinimalRestAssured {

 @Test
 public void checkSiteIsUp() {
	 given().when().get("http://www.acme.com").then().statusCode(200);
 }

}

This minimal example shows how to connect to an HTTP resource through a GET call and verify that the HTTP code 200/success is returned. Notice that we don’t need to use typical assert expressions (like JUnit classes do) as this si done behind the hoods by RESTAssured if there is a mismatch between your expression and the result. Now let’s see a more complex example where we check the body returned as JSON. In order to do that, we first need to import some more classes:

import io.restassured.RestAssured.*
import io.restassured.matcher.RestAssuredMatchers.*
import org.hamcrest.Matchers.*

Now let’s see practical example.

Suppose you have the following JSON returned by the URI “/cars” :

{
  "year": "2001",
  "make": "Ford",
  "model": "Coupe"
}

You can verify that URI returns a status code of 200 and the body content using this fluent API:

@Test
public void testCustomerService() {

        given()
        .when().get("/cars")
        .then()
        .statusCode(200)
        .body(containsString("Ford"),
              containsString("Coupe"));

}

You can also verify exact values:

@Test
public void testCustomerService() {

        given()
        .when().get("/cars")
        .then()
        .statusCode(200)
        .body("year", is(1987));
}

RESTAssured using Parameters

What if your REST Test uses parameters? Here is how you can Test against Path Params:

@Test
public void testListOfUsers() {
    given()
        .contentType(ContentType.JSON)
        .pathParam("id", "12345")
    .when()
        .get("/users/{id}")
    .then()
        .statusCode(200)
        .body("firstName", equalTo("John"))
        .body("Surname", equalTo("Doe"));
}

And here is how you can test agains Query Parameters using as bonus also an Header Verification:

@Test
public void testListOfUsers() {
       given()
            .header("Authorization", authorizationHeader)
            .accept(JSON)
            .queryParam("id", "1")
            .when()
            .get("/users")
            .then()
            .statusCode(200)
            .body("firstName", equalTo("John"))
            .body("Surname", equalTo("Doe"));
   
}

How to validate a response which contains an array of data

Applications which are producing tabular data from a Database, typically a response with an Array of JSON objects. To validate the content of the response, you can reference the single array data as in the following example:

@Test
public void testListOfUsers() {
   RestAssured.given()
        .when().get()
        .then()
        .statusCode(200)
        .body("$.size()", is(3),
                "[0].id", is(1),
                "[0].name", is("Batman"),
                "[1].id", is(2),
                "[1].name", is("Superman"),
                "[2].id", is(3),
                "[2].name", is("Wonder woman")
        );
   
}

Extracting Manually the Response

In some cases, it could be required to fetch the JSON Response so that we perform some actions with it. Here is how to fetch in a JSONArray the Response returned from an HTTP GET Test:

public void testGetListOfUsers() {
    Response response = given()
            .accept(JSON)
            .when()
            .get("/users")
            .then()
            .statusCode(200)
            .contentType(JSON)
            .extract()
            .response();
    String jsonBody = response.getBody().asString();
    try {
        JSONArray usersArray = new JSONArray(jsonBody);
        assertNotNull(usersArray);
        assertTrue(usersArray.length() > 0);
    } catch (JSONException ex) {
        fail(ex.getLocalizedMessage());
    }
}

XML Testing with REST assured

XML can be verified in a similar way. Imagine that a POST request to http://localhost:8080/users returns:

<greeting>
   <firstName>John</firstName>
   <lastName>Doe</lastName>
</greeting>

You can easily perform and verify the firstName with REST assured:

given().
         parameters("firstName", "John", "lastName", "Doe").
when().
         post("/greetXML").
then().
         body("greeting.firstName", equalTo("John")).

If you want to verify both firstName and lastName you may do like this:

given().
         parameters("firstName", "John", "lastName", "Doe").
when().
         post("/greetXML").
then().
         body("greeting.firstName", equalTo("John")).
         body("greeting.lastName", equalTo("Doe"));

Testing other HTTP methods

RESTAssured can be tested to verify all HTTP methods (GET/POST/PUT/DELETE). You can attach the HTTP method in the fluent expression as in the following example:

    @Test
    public void testCar() {

        JsonObject car = Json.createObjectBuilder()
                .add("make", "Fiat")
                .add("model", "500").build();

        // Testing HTTP POST
        given()
                .contentType("application/json")
                .body(car.toString())
                .when()
                .post("/cars")
                .then()
                .statusCode(201);

        // Testing HTTP GET
        given()
                .when().get("/cars")
                .then()
                .statusCode(200)
                .body(containsString("Fiat"),
                      containsString("500"));

        car = Json.createObjectBuilder()
                .add("id", "1")
                .add("make", "Opel")
                .add("model", "Karl").build();

        // Testing HTTP PUT
        given()
                .contentType("application/json")
                .body(obj.toString())
                .when()
                .put("/cars")
                .then()
                .statusCode(204);

        // Testing HTTP DELETE
        given()
                .contentType("application/json")
                .when()
                .delete("/users?id=1")
                .then()
                .statusCode(204);

    }

Compiling REST Assured applications

If you are using Maven to build your project, you need to add the following dependency to your pom.xml:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>4.0.0</version>
    <scope>test</scope>
</dependency>

Also, as REST-assured leverages the Hamcrest matchers to perform its assertions, we must include that dependency as well:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

That’s all! Enjoy coding with RESTAssured