1853 words
9 minutes
OpenML's backend Tech Explained

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.

TIP

JAX-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:

  1. JAX-RS 1.0 / 2.0 (JSR 311 / JSR 339)
  2. JAX-RS 2.1 (JSR 370)
  3. 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 Implementation

While 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:

  1. 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.

  2. 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.

TIP

If 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 @RestController and @RequestMapping. It is tightly bound to the broader Spring ecosystem (e.g., Spring Security, Spring Data).
  • JAX-RS: Uses annotations like @Path and @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 @Path to define routing, and then combine it with explicit verb annotations like @GET and 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 @Produces declaration 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 (or javax.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 DatasetResource above and spins it up using an embedded Jetty container and Jersey’s ResourceConfig:

    import org.glassfish.jersey.server.ResourceConfig;
    public class JerseyApp extends ResourceConfig {
    public JerseyApp() {
    // Register the shared JSR 370 classes manually
    register(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 DatasetResource via its @Path annotation, 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 Divide

The 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 AbstractBinder configuration; 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 method
func 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")
}
OpenML's backend Tech Explained
https://blogs.openml.io/posts/openml-backend/
Author
OpenML Blogs
Published at
2026-06-06
License
CC BY-NC-SA 4.0