Hello World with JAX-RS on Tomcat 10, Jersey and JDK 17
Sometimes the best place to start a new coding experiment is with “Hello World” (HW), the absolute minimum, often borrowed from someone else who has kindly published it for the benefit of others. The fewer assumptions made by the HW project the more useful it will be to the community. Some HW demos tend to make a lot of assumptions (IDE, build mechanism, dependency management etc.) and this presents problems for those who don’t match.
I recently created a starting point for a JAX-RS API running on Tomcat 10.1 and JDK 17, and when I looked around the Web I noticed that similar demos tended to assume Eclipse, or Maven, or Gradle and so on in the development environment. Here I am going to show how to create a working JAX-RS API endpoint without the aid of an IDE or build mechanism. This is as bare-bones as I can get.
But there are some assumptions.
First up, I’m using the latest (as of 2023) Tomcat 10.1 as a container. As Java-based servers go, this is fairly basic and if you can get something running in Tomcat then you can pretty much get it to work in any container. I’m running this on a recent JDK 17 (this one has long-term support). Obviously I’m using OpenJDK rather than some proprietary/expensive version.
Second, as Tomcat doesn’t come with any JEE goodies, I’m using Jersey to provide the implementation for Jakarta REST (JAX-RS).
Libraries
I will leave out the exact version numbers of the libraries as you can find the latest versions online quite easy. For example, in my collection of Jars I have jakarta.ws.rs.-api-3.1.0.jar, which is the current latest. You can find these jars in many places, such as on Maven. Wherever you get them, verify their integrity via the usual checksums.
Jakarta libraries
These are mainly the APIs, not the implementation.
- jakarta.ws.rs-api
- jakarta.inject-api
- jakarta.validation-api
- jakarta.xml.bind-api
- jakarta.servlet.jsp.jstl-api
- jakarta.servlet.jsp.jstl (Apache implementation of taglibs)
Jersey libraries
These come from the Glassfish/Eclipse Jersey reference implementation.
- jersey-common
- jersey-server
- jersey-client
- jersey-container-servlet-core
- jersey-container-servlet
- jersey-guava (repackaged Google-authored utility classes)
- jersey-media-jaxb
- jersey-hk2 (JSR 330, dependency injection)
- hk2-api
- hk2-locator
- hk2-utils
REST Application
The Web Application will be deployed to http(s)://<domain>/demo
and the Jakarta REST application within it will be at sub-path /rest, and it will have one endpoint called “hello” that will accept a GET request containing a ‘key’ parameter, and respond with plain text: “Hello key“. Thus the URL http(s)://<domain>/demo/rest/hello?key=World
will return the text: “Hello World”.
Instead of a web.xml, I am using annotation to declare the class that identifies the REST app, and I am not overriding the base class methods so that the default behaviour will be to search the rest of the app classes for annotation indicating endpoint handlers. This is com/example/DemoApp.java:
package com.example; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("rest") public class DemoApp extends Application { }
This is the implementation of the “hello” endpoint, com/example/Hello.java:
package com.example; import jakarta.ws.rs.*; // Consumes,DefaultValue,GET,Path,Produces,QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @Path("hello") public class Hello { @GET @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Produces({MediaType.TEXT_PLAIN}) public final Response hw(@DefaultValue("key?") @QueryParam("key") String key){ Response.ResponseBuilder rb = Response .ok("Hello "+key,MediaType.TEXT_PLAIN) .status(Response.Status.OK); return rb.build(); } }
Finally, to define the path of the deployment (“/demo”), I need a context.xml
file containing this one line:
<?xml version="1.0" encoding="UTF-8"?><Context path="/demo"/>
To compile the source files at the command line producing the necessary class files, use this:
javac -d CLS --sourcepath SRC --cp JARS:TCLIBS --source 17 --target 17 SRC/com/example/*.java
Notes:
- javac is the Java 17 compiler executable, and may be the absolute filename if necessary
- CLS is the (existing) directory where the class files will be placed
- SRC is the path of the directory containing the com/example/*.java source files
- JARS is a list of the 17 Jar files listed above
- TCLIBS is the list of the Jar files in Tomcat’s lib directory
- Lists are colon:separated in Unix, and semicolon;separated in Windows, so adjust if necessary
The command will produce two files: CLS/com/example/DemoApp.class
and CLS/com/example/Hello.class
That’s it. There is no more code. No more configuration files. The above files need to be packaged into a WAR file (a Zip file with a .war extension) containing the following:
- META-INF/context.xml
- WEB-INF/
- classes/
- com/
- example/
- DemoApp.class
- Hello.class
- example/
- com/
- lib/
- The 17 Jar files go in here
- classes/
Deploy the demo.war file by copying it to Tomcat’s webapps directory and start Tomcat. Test by using a browser to exercise the following path on your server: /demo/rest/hello?key=World
Tools
There are many tools that you can use to make the above process easier. For example, to make the WAR file you can open a command line at a directory containing the pre-filled META-INF and WEB-INF directories, and issue the following command:
jar -cf demo.war
The JDK’s jar tool merely zips the contents recursively to create the WAR file.
You can collect the names of the Jar files into files and use the “@” feature of javac in order to reference these lists instead of having all the files individually in the command line. You can use a build tool to track these dependency lists, or use a similar feature within your preferred IDE. Using tools makes this process much easier than the manual steps I outlined above, though I hope you can now see what is happening under the hood regardless of your chosen tools.
Categorised as: Coding, Protocols & Specs, Technology, Web