How to create REST API below JEE 6

Nowadays, there are application servers and applications based on older Java EE standards (< 6) still to be found. Anyhow, such applications often need to be complemented with some kind of REST API. The best way is to update your JEE server to more recent version, where JAX-RS is already present. Weblogic 11(g) is a special category, supporting JAX-RS to some degree. But the support is rather ugly and as shown below, more up-to-date solutions exist.

Fully functional example can be viewed and cloned from GitHub

The mission

  1. Create and deploy an application with a REST API interface
  2. Validate incoming requests
  3. Handle exceptions safely
  4. Support both JSON and XML format for request and response
  5. Document the API automatically

The solution

There is a JAX-RS (JSR 311) and later JAX-RS 2.0 (JSR 339) reference implementation named Jersey. Jersey can however be used solely, being absolutely compatible with older JEEs. Servlet 3+ is not required, Servlet 2 is completely fine.

Documentation can be automatically generated by Enunciate, which is also able to generate Swagger.

Adding Jersey dependencies to the project

A lot of dependencies has to be addded to the project. Feel free to use newer version, if available. Just make sure the groupId tag starts with org.glassfish.jersey. Ignore older version starting com.sun.jersey. Please note that those dependencies are solely for server-side REST API.

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
    <version>2.23.2</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-common</artifactId>
    <version>2.23.2</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet-core</artifactId>
    <version>2.23.2</version>
    <type>jar</type>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.23.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-bean-validation</artifactId>
    <version>2.23.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
	<groupId>com.webcohesion.enunciate</groupId>
	<artifactId>enunciate-core-annotations</artifactId>
	<version>2.6.0</version>
</dependency>

Brief explanation:

  • jersey-* dependencies provide the Jersey servlet, API annotations, JSON serialization/deserialization abilities and bean validation.
  • jackson-* dependencies must be included in order for the XML serialization/deserialization to work. Request/response POJOS must be further annotated with Jackson annotations in order to work properly.
  • enunciate-core-annotations provides documentation annotations used by Enunciate during the automatic documentation generation.

Registering a servlet

Automatic, easy to use configuration is not present in older version of Java EE. Therefore, an “old-fashioned” servlet has to be registered and configured.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <display-name>REST API Sample</display-name>
    <servlet>
        <servlet-name>RestApplication</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.beanValidation.enableOutputValidationErrorEntity.server</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.classnames</param-name>
            <param-value>
                com.rest.HelloEndpoint
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>RestApplication</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Automatic class scanning can be also enabled by simply configuring the servlet for automatic endpoint discovery, as stated in the official documentation. Recursive scanning should be enabled to force jersey scan sub-packages.

<init-param>
    <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>
        com.rest
    </param-value>
</init-param>
<init-param>
    <param-name>jersey.config.server.provider.scanning.recursive</param-name>
    <param-value>true</param-value>
</init-param>

Creating the endpoint

The endpoint is a regular Java class. The package and classe’s name must be aligned with Jersey servlet configuration in web.xml - mentioned above. If automatic endpoint discovery is enabled, the endpoint must be present in a package Jersey performs the scan in.

The @ResponseCode, @StatusCodes and @TypeHint annotation are not JAX-RS annotations. These are Enunciate annotations and they serve for documentation purpose only.

package com.rest;

import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


@Path("/hello")
@StatusCodes({
    @ResponseCode(code = 500, condition = "Internal error of the service"),
})
public class HelloEndpoint {

  
    /**
     * Returns a static greeting
     *
     * @return a Greeting object with greeting String in any language.
     */
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @TypeHint(Greeting.class)
    @StatusCodes({
        @ResponseCode(code = 200, condition = "Greeting successful")
    })
    public Response getEvents();
    	Greeting greeting = new Greeting("Bonjour ! I love French food.");
    	return Response.ok(greeting).build();
    }


}

The Greeting class is a simple POJO.

package com.rest;

public class Greeting {

	private String greeting;

	public Greeting(String greeting){
		this.greeting = greeting;
	}

	public String getGreeting(){
		return greeting;
	}

	public void setGreeting(String greeting){
		this.greeting = greeting;
	}
  
}

Done ! Nothing else is required. By deploying your application, and calling GET http://{host}:{port}/api/hello, you should recieve a response in JSON format. Always remember, the first MediaType.* in the @Produces annotation is considered to be the default response format. Adding headers Accept: application/xml forces Jersey to serialize the response in XML format.

Java Validation API can be used for any request parameters or body POJOS - Jersey will automatically validate the request and generate a HTTP 400 Bad Request when the request is invalid.

It might be a good idea to incorporate Enterprise Java Beans and use them as Endpoint classes directly. As Adam Bien showed in his video, it might be a good idea performance-wise.

Generating documentation

This is an additional step. For documentation, Enunciate proved to be a great tool, configurable both with Maven and Gradle. Enunciate is able to generate basic documentation straight out of your code with very little configuration. However, there is a JAR containing additional annotations. When such annotations are present, Enunciate is able to generate much more understandable documentation. You can specify return states and define situations when those states are return, you can tell Enunciate about endpoint’s return type, override default examples with your own and of course, there is more. This dependency is already present in the Maven pom.xml configuration chapter (above).

It is compatible with both Maven and Gradle . In Maven (used in this article as a reference tool), simply add Enunciate maven plugin. Don’t use version 2.6.0, there is a bug and examples for Arrays/Lists are generated incorrectly.

<plugins>
    <plugin>
        <groupId>com.webcohesion.enunciate</groupId>
        <artifactId>enunciate-maven-plugin</artifactId>
        <version>2.5.0</version>
        <configuration>
            <configFile>${project.basedir}/enunciate/enunciate.xml</configFile>
        </configuration>
        <executions>
            <execution>
                <id>assembleEnunciate</id>
                <goals>
                    <goal>assemble</goal>
                </goals>
                <configuration>
                    <docsDir>${project.build.directory}/docs</docsDir>
                </configuration>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.3.3</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>

Enunciate works out of the box without any configuration. In this example, a file with basic configuration is expected. It is put in enunciate/enunciate.xml file, as seen in the plugin configuration. In this example, Java client generation is left enabled, as well as Swagger generation. Both XML and JSON examples are generated. Detailed configuration possibilities can be found in Enunciate’s Wiki.

<?xml version="1.0" encoding="UTF-8"?>
<enunciate
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://enunciate.webcohesion.com/schemas/enunciate-2.6.0.xsd">
    <title>OTA/EPS Wrapper</title>
    <application root="/api" />
    <modules>
        <docs disabled="false" basePath="/"></docs>
        <jackson disabled="false"> </jackson>
        <jackson1 disabled="true"> </jackson1>
        <c-xml-client disabled="true"></c-xml-client>
        <obj-c-xml-client disabled="true"></obj-c-xml-client>
        <csharp-xml-client disabled="true"></csharp-xml-client>
        <php-xml-client disabled="true"></php-xml-client>
        <php-xml-client disabled="true"></php-xml-client>
        <jaxws disabled="true"/>
        <swagger dir="swagger" disabled="false"/>
        <docs disableResourceLinks="true"/>
    </modules>
    <api-classes>
        <include pattern="com.rest.**"/>
    </api-classes>

</enunciate>

The documentation is then generated into target/docs/ directory. This is also present in the plugin configuration.

Example

Fully functional example can be viewed and cloned from GitHub