JAX-RS & Jakarta REST
JAX-RS (Java API for RESTful Web Services) is a standardized Java specification that provides support for creating web services according to the Representational State Transfer (REST) architectural pattern. Managed by the Eclipse Foundation and renamed to Jakarta RESTful Web Services, it allows developers to expose Java objects as REST endpoints using declarative metadata.
Spec Source Code of JSR 370
JSR 370 is the specification for JAX-RS 2.1 (Java API for RESTful Web Services). Because Java EE was transitioned to the Eclipse Foundation and rebranded as Jakarta EE, the specification’s source code has moved a few times over the years.
TIPJAX-RS is the overarching name of the technology, while JSR 370 was simply the specific tracking number for version 2.1 of that technology.
The name progression went like this:
- JAX-RS 1.0 / 2.0 (JSR 311 / JSR 339)
- JAX-RS 2.1 (JSR 370)
- Jakarta RESTful Web Services (The modern name, following the migration to the Eclipse Foundation)
Depending on whether we want the modern repository or the historical archive, here is where we can find the specification’s source code.
The Modern Eclipse Repository
The specification is now known as Jakarta RESTful Web Services. The active source code for the API, the TCK (Technology Compatibility Kit), and the specification document itself lives in the Eclipse Foundation’s GitHub repository at https://github.com/jakartaee/rest.
If we browse that repository, we will find a directory named jaxrs-spec (or simply spec in newer branches). This
folder contains the raw markup files - typically AsciiDoc or DocBook - used to generate the specification document. To
view the source code exactly as it was during the JAX-RS 2.1 era, we can explore the 2.1 or 2.1.x release tags
The Historical Archives
If we are looking for the original, unaltered JCP source code precisely as it was drafted under Oracle before the Jakarta EE transition, those repositories are preserved but frozen as read-only.
The original specification source was hosted at https://github.com/jax-rs/spec. When the transition to open source began, it was temporarily moved under the Java EE organization at https://github.com/javaee/jax-rs-spec. Both of these repositories are now archived, but they provide a perfect historical snapshot of the JSR 370 development process.
Final Release Artifacts
If we you do not need the raw markup source code and simply want to read the final compiled specification, the official JSR 370 PDF and the accompanying Javadoc API are still hosted on the Java Community Process website. We can download the final release artifacts directly from the Oracle JCP download page for JSR 370.
Jersey: The Reference Implementation
When Oracle/Sun designed a specification like JSR 370, they were required to build a concrete, working example to prove
the specification worked. Jersey was built by Sun/Oracle as that official Reference Implementation. Because it was
designed in the era of application servers, Jersey is highly dynamic. It scans an application at runtime using heavy
Java reflection to find @Path and @GET annotations.
Quarkus: The Modern, Build-Time ImplementationWhile Jersey has been updated to support modern versions, its fundamental architecture was conceived in a time before cloud-native containers and instant startups were the primary goal.
Quarkus, on the other hand, is a modern framework built from scratch by Red Hat for the cloud and Kubernetes era. Quarkus developers looked at the JAX-RS (Jakarta REST) programming model, loved the annotations, but hated the slow runtime reflection used by older tools like Jersey. Instead of doing everything at runtime, Quarkus shifts all that heavy annotation scanning to compile-time. When we build a Quarkus app, it reads application’s JAX-RS annotations and generates optimized, direct bytecode.
Quarkus doesn’t actually use Jersey under the hood. It uses RESTEasy Reactive, which is an entirely separate, highly optimized implementation of the exact same JAX-RS specification.
Although faced with fierce competition from modern cloud-native frameworks, Jersey is not outdated, but it occupies a very specific professional niche:
-
Light, Standalone Web Servers
If our goal is to build an exceptionally lean, high-throughput microservice without the massive footprint of a comprehensive framework, coupling Jersey with an embedded servlet container like Jetty or Grizzly is a top-tier architectural choice. It allows us to build a standard-compliant, lightning-fast REST API with absolute control over our dependencies.
-
Standard-Compliant Architectures
For teams enforcing a strict separation between a specification API and the underlying framework engine, Jersey remains the premium reference implementation. We can swap Jersey for another Jakarta REST implementation (like RESTEasy) later down the road with minimal Friction.
TIPIf you are running a service built on Jersey 1.x or 2.x using old
javax.*packages, that specific stack is legacy.However, running a modern Jersey 3.x or 4.x application on a light, containerized engine is completely modern. It isn’t outdated; it is simply a deliberate, minimalist architectural choice favored by developers who prioritize explicit control and standards compliance over comprehensive framework ecosystems like Spring.
Spring Does Not Implement JAX-RS
Spring has its own native specification and ecosystem for building REST APIs called Spring MVC. It operates as a fully functional, alternative way to handle REST endpoints without adhering to the JAX-RS standard
While both achieve the same goal, they differ in design and annotation:
- Spring MVC: Uses annotations like
@RestControllerand@RequestMapping. It is tightly bound to the broader Spring ecosystem (e.g., Spring Security, Spring Data). - JAX-RS: Uses annotations like
@Pathand@GET. It is an official Java EE (now Jakarta EE) specification with multiple independent implementations like Jersey or Apache CXF.
The underlying configuration mechanisms highlight how differently they treat the HTTP protocol:
-
JAX-RS relies on composition. We use
@Pathto define routing, and then combine it with explicit verb annotations like@GETand explicit mime-type filters like@Produces.// JAX-RS (JSR 370 / Jakarta REST)@Path("/books")public class BookResource {@GET@Path("/{id}")@Produces(MediaType.APPLICATION_JSON)public Book getBook(@PathParam("id") Long id) { ... }} -
Spring prioritizes convenience and convention over strict configuration. It wraps routing and verbs together into single annotations like
@GetMapping. It also infers context implicitly—for example, if a method returns a POJO and the request header asks for JSON, Spring automatically leverages Jackson to serialize it without requiring an explicit@Producesdeclaration on every method.// Spring Web MVC@RestController@RequestMapping("/books")public class BookController {@GetMapping("/{id}")public Book getBook(@PathVariable Long id) { ... }}
Jakarta REST
The name “JAX-RS”, though, is increasingly viewed as an older term. Architecturally, the specification is highly vibrant and under active, modern development. However, the ecosystem has gone through a massive rebranding and structural evolution that changes how it is perceived today. It has been renamed to Jakarta RESTful Web Services (or simply Jakarta REST).
When Oracle moved Java EE to the Eclipse Foundation, they retained the trademark on the “Java” name. This forced a
massive package migration from javax.ws.rs.* to jakarta.ws.rs.*.
The community didn’t just rename the project; they stripped out legacy dependencies from the early 2000s. For example, recent specifications (like Jakarta REST 4.0) completely decoupled the framework from old JAXB XML requirements and removed obsolete ManagedBeans components. It is now a lean, modern, JSON-first specification that runs natively on virtual threads (Project Loom).
The Rise of Next-Gen Runtimes (Quarkus and Helidon)
Modern, ultra-fast cloud-native frameworks have chosen the Jakarta REST annotation standard as their default programming model:
- Quarkus: Red Hat’s Kubernetes-native Java framework uses RESTEasy (a Jakarta REST implementation) as its core engine for building REST APIs.
- Helidon: Oracle’s microservice framework relies heavily on Jakarta REST for its routing model.
Because these frameworks compile down to native binaries using GraalVM, a JAX-RS styled web service can achieve sub-millisecond startup times and memory footprints small enough to rival Go or Node.js.
How OpenML Embraces JAX-RS
So far we have reached the following wrap up:
- Specification: JAX-RS (specifically version 2.1 via JSR 370, now known as Jakarta REST) defines the rules and annotations.
- Traditional Implementation (application server): Jersey
- Modern Implementation (cloud-native): Quarkus
Each OpenML’s Java-based webservice maintains both implementation with a “shared engine strategy” by isolating core business logic and JSR 370 into framework-agnostic Maven module:
-
The Core Resource Module
This module contains purely our JSR 370 resources, utilizing only standard
jakarta.ws.rs(orjavax.ws.rs) annotations. It has zero awareness of whether Jersey or Quarkus is running it.Terminal window package io.openml.resource;import jakarta.ws.rs.GET;import jakarta.ws.rs.Path;import jakarta.ws.rs.Produces;import jakarta.ws.rs.core.MediaType;@Path("/dataset")public class DatasetResource {@GET@Produces(MediaType.APPLICATION_JSON)public String getDatasetInfo() {return "{\"status\": \"active\"}";}} -
The Jersey Bootstrapper Module
The traditional deployment module of OpenML webservice imports
DatasetResourceabove and spins it up using an embedded Jetty container and Jersey’sResourceConfig:import org.glassfish.jersey.server.ResourceConfig;public class JerseyApp extends ResourceConfig {public JerseyApp() {// Register the shared JSR 370 classes manuallyregister(io.openml.resource.DatasetResource.class);}} -
The Quarkus Bootstrapper Module
To run those exact same endpoints inside Quarkus, WE create a parallel deployment module. We pull in the Quarkus REST extension via
pom.xml:<dependency><groupId>io.quarkus</groupId><artifactId>quarkus-rest</artifactId></dependency><dependency><groupId>io.openml</groupId><artifactId>core-resource-module</artifactId> <!-- Contains DatasetResource --><version>${project.version}</version></dependency>Quarkus automatically scans application’s classpath at compile-time, locates
DatasetResourcevia its@Pathannotation, and hooks it directly into its ultra-fast, reactive HTTP engine (RESTEasy Reactive). We don’t have to alter a single line of application’s endpoint logic; it instantly runs with build-time optimizations.
Handling the Dependency Injection DivideThe biggest friction point we might encounter when maintaining both environments simultaneously is Dependency Injection (DI) because
- Jersey natively relies on the HK2 injection engine (or manual wiring).
- Quarkus strictly relies on its own compile-time CDI implementation called ArC (handling
@ApplicationScoped,@Inject, etc.).To keep OpenML’s backend maintainable across both systems, we must be sure to keep the shared JSR 370 resource classes lean and free of direct DI framework annotations. Use standard constructor injection wherever possible. When running in Jersey, we can manually bind the dependencies in
AbstractBinderconfiguration; when running in Quarkus, ArC will automatically resolve the constructor parameters at compile-time without requiring framework-specific boilerplate.
Go-Based Webservice
There is no official, language-wide specification equivalent to JAX-RS (Jakarta RESTful Web Services) in Go whose
ecosystem has a fundamentally different philosophy than Java. While Java relies heavily on formal enterprise
specifications (JCP/Jakarta) and heavy runtime reflection or annotation-driven frameworks, Go favors explicit
composition, minimal abstractions, and compile-time clarity. Go does not have built-in runtime annotations (like @Path
or @GET) or an official “enterprise specification body” for web interfaces.
The Go ecosystem addresses things like mapping URLs to code handlers, data serialization, and API contract adherence through several standard patterns and open-source libraries.
In Java, you almost always need a third-party framework or a Jakarta container to handle REST APIs cleanly. In Go, the standard library is powerful enough to handle production web services natively.
Using Go’s built-in net/http package, we can declare paths, HTTP methods, and variables directly without external
dependencies:
package main
import ( "encoding/json" "net/http")
type User struct { ID string `json:"id"` Name string `json:"name"`}
// Equivalent to a JAX-RS Resource class methodfunc getUserHandler(w http.ResponseWriter, r *http.Request) { // Equivalent to @Path("/users/{id}") id := r.PathValue("id")
user := User{ID: id, Name: "Alice"}
w.Header().Set("Content-Type", "application/json") // Equivalent to @Produces json.NewEncoder(w).Encode(user)}
func main() { mux := http.NewServeMux() // Equivalent to @GET and @Path mux.HandleFunc("GET /users/{id}", getUserHandler)
http.ListenAndServe(":8080", mux)}For those who enjoy the declarative routing, middleware, and ease-of-use of JAX-RS implementations like Jersey or RESTEasy, Go has highly popular open-source web frameworks that streamline this process. Gin is one of the most widely adopted frameworks in the ecosystem. It provides a fast, explicit routing tree, automatic JSON binding, and validation.
package main
import "github.com/gin-gonic/gin"
func main() { r := gin.Default()
// Explicit routing, equivalent to @GET and @Path r.GET("/users/:id", func(c *gin.Context) { id := c.Param("id") // Automatic serialization, equivalent to @Produces(MediaType.APPLICATION_JSON) c.JSON(200, gin.H{"id": id, "name": "Alice"}) })
r.Run(":8080")}