Tobias Erdle's Blog

Development Engineer working with different technologies. Former Jakarta MVC & Eclipse Krazo Committer. All views are my own.

Github: erdlet | E-Mail: blog (at) erdlet (punkt) de

Modern web applications with Jakarta EE and Tomcat: Part 1 - Setting up Jakarta REST with Jersey

This article is the beginning of a small series of tutorials explaining the implementation of a modern web application based on selected Jakarta EE APIs and Apache Tomcat as a representative of servlet containers. The goal of the series is to implement a maintainable and lightweight web application based on standardized APIs with as few dependencies as possible. Topics such as content negotiation and testing will also be discussed.

However, unfortunately not every detail of the specifications used can be covered, as this would fill the content of an entire book. Therefore, in each article, links to the respective specifications and documentation are provided as precisely as possible, so that the reader can dive deeper into the topic at any time.

Planned topics

Over the next few weeks, the following topics will be worked on one by one. As soon as the corresponding article has been published, it will also be linked here.

If I think of any other points during the preparation of the articles, I will add to the list accordingly.

Prerequisites and initial situation

The following chapters present the prerequisites and starting point for this series of articles.

Technical requirements

To try the code written in the articles, one needs to install the following dependencies to their system:

Attention: Tomcat 10 and newer is using the jakarta.* namespace of Jakarta EE APIs.

Project layout

The application to be developed is to become a small blogging system that processes two subject objects, namely the post and the comments on a post. From an architectural point of view, this application orients itself on the principles of self-contained systems.

This class diagram will introduce the model the application uses.

Domain model class diagram

A Post is a text like this one and has a title, its content, a publication date, as well as a list of the comments written to it.

A Comment consists of its author, the content, a publication date and a reference to the post.

Those models are placed inside the de.erdlet.blogging.blog.model package, which is part of this initial project structure shown below.

.
├── .src/
│   └── main/
│       ├── java/
│       │   └── de.erdlet.blogging.blog.model/
│       │       ├── Post.java
│       │       └── Comment.java
│       └── resources/
│           └── logback.xml
└── pom.xml

Before starting with the actual implementation of the application, the initial pom.xml will be shown.

<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>de.erdlet.blogging</groupId>
    <artifactId>self-contained-system-tomcat</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>webapp</name>

    <description>
        This application demonstrates a simple self-contained system based
        on Jakarta EE APIs and a servlet container as runtime environment.
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.release>17</java.release>
    </properties>

    <build>
        <finalName>blogging-app</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>${java.release}</release>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

At the beginning no dependencies are entered here, so that an "empty" project is started. Only in the <build> configuration, the maven-war-plugin as well as the maven-compiler-plugin are configured so that no errors occur at startup and the correct Java version is used.

Setting up Jakarta RESTful Web Services and Jersey

Before the technical part starts, the motivation behind Jakarta RESTful Web Services shall be introduced. In general, according to its specification page, Jakarta RESTful Web Services provides a foundational API to develop web services following the Representational State Transfer (REST) architectural pattern.. This API makes it possible to write action-based HTTP endpoints which can use the whole power of the HTTP protocol to serve content.

With this knowledge, the actual implementation of the application can begin. The following sections can be understood as step-by-step instructions and can be followed accordingly.

Adding the necessary dependencies

To use Jakarta RESTful Web Services and Jersey in an application outside of a Jakarta EE Application Server, a few dependencies need to be added to the application. These can be seen in the following snippet of pom.xml.

        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>3.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>3.0.4</version>
        </dependency>

The first dependency, namely jakarta.ws.rs:jakarta.ws.rs-api, is used to provide the necessary API for Jakarta RESTful Web Services (from now on, within this and the next articles, Jakarta REST will be used as abbreviation). Based on this API, other implementations, such as RESTEasy, could theoretically be used. In the further course, however, the reference implementation Eclipse Jersey is used. The other two dependencies, org.glassfish.jersey.core:jersey-server and org.glassfish.jersey.containers:jersey-container-servlet, are necessary to provide the server side components for Jakarta REST (jersey-server) and the adapters to work on a plain servlet container (jersey-servlet-container).

Adding an Application class

To indicate to Jersey that the project is a Jakarta REST application, a class derived from jakarta.ws.rs.core.Application must first be implemented and annotated with jakarta.ws.rs.ApplicationPath. This class is created in de.erdlet.blogging.blog and has no specific naming scheme. In this case, it's named JakartaRestApplication.

package de.erdlet.blogging.blog;

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

@ApplicationPath("")
public class JakartaRestApplication extends Application {
}

The ApplicationPath specifies the root URI of the Jakarta REST application which is relative to the context path of the entire application deployed to the server. For example, if the application is served under localhost:8080/foo, an Jakarta REST application with @ApplicationPath("bar") will be served under localhost:8080/foo/bar. This mechanism can also be used to serve more than one Jakarta REST application per web application, but this is out of scope of this article.

Anyway, in this example, the Jakarta REST application is served as context root, so every request is being processed by it.

Adding a first resource

After an Application class is present in the application, a resource can be processe by Jakarta REST and its underlaying implementation. The PostsResource, created in de.erdlet.blogging.blog.api, is a simple, but working, example.

package de.erdlet.blogging.blog.api;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

@Path("posts")
public class PostsResource {

    @GET
    public Response index() {
        return Response.ok("Posts resource index").build();
    }
}

By adding the jakarta.ws.rs.Path annotation on top of the class, the Jakarta REST implementation registers this class as one of its resources. The PostsResource will then be mapped on the /posts URI relative to the Jakarta REST ApplicationPath.

To get a first response from this resource, the method index() is annotated with jakarta.ws.rs.GET, which declares this as the handler for the HTTP request on GET /posts. By using jakarta.ws.rs.core.Response as a result, the method returns all necessary data for constructing a valid HTTP response. This response contains at least the HTTP status, which is 200 for OK in this case. Additionally, this example returns a text body containing Posts resource index to check, if the resource is processed correct. For other use-cases, Response provides a lot of convenience methods which help to develop a fine grained resource using the whole power of HTTP.

Trying the example

Now the example can be built and deployed to a Tomcat server. On the authors machine, the web application is deployed on the /blogging-app URI, so the previously created resource needs to be called with GET localhost:8080/blogging-app/posts. If everything is correct, the response from cURL call curl -i localhost:8080/blogging-app/posts shall look like this:

/posts
HTTP/1.1 200 
Content-Type: text/plain
Content-Length: 20
Date: Fri, 08 Jul 2022 12:22:08 GMT

Posts resource index

As intended, a status 200 is returned and the body contains the text Posts resource index. All other fields are filled by the runtime automatically. Manipulating those is a more advanced topic not discussed here.

Conclusion

After the necessary introduction, this article shows how easy it is to add Jakarta RESTful Webservices and Jersey to a web application running on a servlet container. These basics form a solid foundation for more advanced topics and modern, lightweight applications as they are desired in today's world. In addition, only standardized APIs are used, which avoids vendor lock-in.

Upcoming topic: Setting up Jakarta Bean Validation

The next article will cover how to set up Jakarta Bean Validation and handling validation errors.

Resources

Edits