Deploying Jersey RESTful Services on Tomcat 10 and Standalone Modes with JDK 17

Environment Prerequisites

Install JDK 17 from the official archive to ansure compatibility with modern Jakarta EE standards. While newer versions exist, JDK 17 offers long-term support and stability. Additionally, download Apache Tomcat 10, which implements Jakarta EE 9, requiring namespace changes from javax to jakarta.

Project Initialization and Dependencies

Create a Dynamic Web Project in Eclipse. Since build tools like Maven are excluded, manually manage dependencies. Download the core Jersey JARs (jersey-server, jersey-container-servlet, jersey-media-json-jackson) and logging libraries (logback-classic, slf4j-api). Place all .jar files into the WEB-INF/lib directory.

Implementing Resource Classes

Define the API endpoints using Jakarta annotations. The following class handles POST requests with basic validation and JSON response construction.

package com.example.api;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Path("/operations")
public class ServiceEndpoint {

    private static final Logger log = LoggerFactory.getLogger(ServiceEndpoint.class);
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @POST
    @Path("/execute")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    public Response processPayload(String input) {
        if (input == null || input.trim().isEmpty()) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity("{\"error\":\"Missing input data\"}")
                    .build();
        }

        log.info("Received payload: {}", input);
        String timestamp = LocalDateTime.now().format(formatter);
        
        String jsonResponse = String.format(
            "{\"status\":\"success\",\"time\":\"%s\",\"echo\":\"%s\"}",
            timestamp, input
        );

        log.info("Response generated: {}", jsonResponse);
        return Response.ok(jsonResponse).build();
    }

    @GET
    @Path("/status")
    @Produces(MediaType.TEXT_PLAIN)
    public String checkHealth() {
        return "Service Online";
    }
}

Configuring the Web Descriptor

Update web.xml to register the Jersey ServletContainer. Configure the initialization parameters to scan the specific package containing resource classes.

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="5">
    <servlet>
        <servlet-name>JerseyWebServlet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jakarta.ws.rs.Application</param-name>
            <param-value>com.example.api.ApiConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JerseyWebServlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

Create the configuration class to enable package scanning.

package com.example.api;

import org.glassfish.jersey.server.ResourceConfig;

public class ApiConfig extends ResourceConfig {
    public ApiConfig() {
        packages("com.example.api");
    }
}

Deploy the application to Tomcat 10. Access the endpoints via:

  • GET: http://localhost:8080/ProjectName/api/operations/status
  • POST: http://localhost:8080/ProjectName/api/operations/execute

Standalone Execution With out Container

Jersey can run embedded using Grizzly HTTP server, removing the need for Tomcat. Thiss approach is suitable for microservices or testing environments.

package com.example.standalone;

import com.example.api.ApiConfig;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;

public class EmbeddedLauncher {
    private static final Logger log = LoggerFactory.getLogger(EmbeddedLauncher.class);
    private static final String BASE_URI = "http://0.0.0.0:9090/";

    public static void main(String[] args) {
        try {
            log.info("Initializing embedded server...");
            ApiConfig config = new ApiConfig();
            
            HttpServer server = GrizzlyHttpServerFactory.createHttpServer(
                URI.create(BASE_URI), 
                config
            );
            
            server.start();
            log.info("Server started at {}", BASE_URI);
            
            // Keep alive
            Thread.currentThread().join();
            
        } catch (Exception ex) {
            log.error("Startup failed: {}", ex.getMessage());
        }
    }
}

When running in standalone mode, the context path changes. Access points become:

  • GET: http://localhost:9090/operations/status
  • POST: http://localhost:9090/operations/execute

No web.xml mapping is required in this mode as the base URI is defined directly in the server factory.

Tags: jersey RESTful java jdk17 tomcat10

Posted on Thu, 28 May 2026 21:55:25 +0000 by Paul_Bunyan