Modern web applications with Jakarta EE and Tomcat: Part 1 - Setting up Jakarta REST with Jersey
08 Jul 2022 (last modified 26 Jul 2022) - Tobias ErdleThis 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.
- Setting up Jakarta REST with Jersey (this article)
- Setting up Jakarta Bean Validation
- Setting up Jakarta Contexts and Dependency Injection
- Setting up Jakarta Persistence
- Setting up Jakarta MVC for server-side rendered frontends
- Content Negotiation with Jakarta REST
- Unit and integration testing
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:
- JDK 17, e. g. from Eclipse Adoptium
- Maven 3.8.x
- Tomcat 10 or newer
- some code editor or IDE
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.
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
- 26 Juli 2022: Reference to database migration tool removed