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.