Series MapLesson 13 / 112
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Start HereOrdered learning track

Jersey vs Jakarta REST Standard

Memisahkan API standar Jakarta REST dari implementasi Jersey: apa yang portable, apa yang Jersey-specific, dan apa yang wajib diverifikasi di runtime internal

12 min read2399 words
PrevNext
Lesson 13112 lesson track01–21 Start Here
#jersey#jax-rs#jakarta-rest#implementation+3 more

Part 013 — Jersey vs Jakarta REST Standard

Fokus part ini: membangun batas mental yang jelas antara Jakarta RESTful Web Services / JAX-RS sebagai specification dan Jersey sebagai implementation.

Di codebase enterprise, sering terjadi pencampuran istilah:

"JAX-RS endpoint"
"Jersey resource"
"Jakarta REST service"
"REST controller"
"Servlet endpoint"

Sebagian istilah benar secara konteks, sebagian terlalu longgar.

Untuk senior engineer, perbedaannya penting karena:

  • standard menentukan contract portable,
  • implementation menentukan runtime behavior nyata,
  • container menentukan lifecycle dan deployment behavior,
  • platform internal menentukan bagaimana service benar-benar dioperasikan.

Kalau batas ini kabur, PR review akan lemah. Engineer bisa salah mengira behavior tertentu adalah standar, padahal hanya berlaku karena Jersey, HK2, GlassFish, Grizzly, Servlet container, atau konfigurasi internal tertentu.


1. The Core Distinction

Mental model paling sederhana:

Jakarta REST / JAX-RS
  = specification + API contracts

Jersey
  = salah satu implementation dari specification tersebut

Servlet / GlassFish / Grizzly / Tomcat / Jetty
  = runtime/container tempat implementation berjalan

Internal platform/configuration
  = cara perusahaan mengemas, men-deploy, mengamankan, dan mengobservasi service

Diagram:

flowchart TD A[HTTP Request] --> B[Container / HTTP Runtime] B --> C[JAX-RS Implementation] C --> D[Jakarta REST API Model] D --> E[Resource Method] B -.examples.-> B1[Servlet Container: Tomcat / Jetty] B -.examples.-> B2[GlassFish] B -.examples.-> B3[Embedded Grizzly] C -.example.-> C1[Jersey] C -.other examples.-> C2[RESTEasy] C -.other examples.-> C3[Apache CXF] D -.standard.-> D1[@Path / @GET / @Produces] D -.standard.-> D2[MessageBodyReader / Writer] D -.standard.-> D3[ContainerRequestFilter]

Important boundary:

If it comes from jakarta.ws.rs.*, it is likely Jakarta REST standard.
If it comes from org.glassfish.jersey.*, it is Jersey-specific.
If it comes from jakarta.servlet.*, it is Servlet-specific.
If it comes from org.glassfish.hk2.*, it is HK2-specific.
If it comes from jakarta.enterprise.*, it is CDI-specific.

This is not just naming trivia. It tells you what can be migrated, tested, configured, and debugged using standard knowledge versus implementation-specific knowledge.


2. Why This Matters in Enterprise Systems

In small services, implementation details may feel invisible.

In enterprise systems, they become operational facts:

ConcernStandard Jakarta REST?Jersey-specific?Runtime/platform-specific?
@Path, @GET, @POSTyesnono
Request matching rulesmostly yesedge behavior may varyproxy path may affect
MessageBodyReader/Writeryesprovider registration variesclasspath/module config matters
ResourceConfignoyesno
HK2 bindingnoyes/Jersey ecosystemno
CDI integrationJakarta CDI standard, integration-specificJersey integration-specificcontainer-specific
Servlet filter chainnonoServlet container
Embedded Grizzly servernoJersey/Grizzly usageruntime-specific
Auto-discovery behaviornooften Jersey-specificclasspath-specific
Startup error messagesnoyesdeployment-specific
Production timeout behaviornopartlymostly container/gateway/platform

A production bug may be caused by any layer.

Example:

Symptom:
  Client receives 415 Unsupported Media Type.

Possible cause by layer:
  Standard layer:
    @Consumes does not match Content-Type.

  Jersey layer:
    JSON provider module not registered or auto-discovery disabled.

  Container/platform layer:
    Gateway rewrites/removes Content-Type.

  Internal implementation layer:
    Custom request filter rejects vendor media type before resource matching.

If you only know the annotation, you debug too shallowly.


3. Jakarta REST Standard Surface

The Jakarta REST standard provides the programming model.

Common standard package:

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import jakarta.ws.rs.ext.*;
import jakarta.annotation.*;

Common standard annotations:

@Path
@GET
@POST
@PUT
@DELETE
@PATCH // available depending on API version / implementation support context
@Consumes
@Produces
@PathParam
@QueryParam
@HeaderParam
@CookieParam
@FormParam
@DefaultValue
@Context
@Provider

Common standard types:

Response
MediaType
UriInfo
HttpHeaders
Request
SecurityContext
Application
Feature
FeatureContext
Configuration
ExceptionMapper<T>
MessageBodyReader<T>
MessageBodyWriter<T>
ContainerRequestFilter
ContainerResponseFilter
ReaderInterceptor
WriterInterceptor

When code uses these types, you are usually looking at portable Jakarta REST concepts.

Example:

@Path("/quotes")
@Produces(MediaType.APPLICATION_JSON)
public class QuoteResource {

  @GET
  @Path("/{quoteId}")
  public Response getQuote(@PathParam("quoteId") String quoteId) {
    return Response.ok().build();
  }
}

This is standard JAX-RS/Jakarta REST style.

A different implementation could theoretically run this resource as long as registration, dependency injection, and providers are compatible.


4. Jersey-Specific Surface

Jersey-specific code commonly appears under:

org.glassfish.jersey.*

Examples:

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.internal.inject.AbstractBinder;

Typical Jersey-specific concepts:

  • ResourceConfig,
  • Jersey Feature usage and registration style,
  • Jersey properties,
  • Jersey auto-discovery,
  • Jersey media modules,
  • Jersey multipart support,
  • Jersey test framework,
  • Jersey client implementation,
  • HK2 integration,
  • Jersey tracing/debug properties,
  • Jersey-specific injection bridges.

Example:

public class QuoteApplication extends ResourceConfig {
  public QuoteApplication() {
    packages("com.example.quote.api");
    register(JacksonFeature.class);
    register(MultiPartFeature.class);
    register(new QuoteBinder());
    property("jersey.config.server.tracing", "ALL");
  }
}

This is not pure standard Jakarta REST. It is Jersey application bootstrap code.

That is not bad. It is often the right tool.

But you must know the consequence:

ResourceConfig-based bootstrap is portable only within Jersey-style runtimes.

5. Standard Application vs Jersey ResourceConfig

Standard Jakarta REST has Application:

import jakarta.ws.rs.core.Application;

@ApplicationPath("/api")
public class QuoteApplication extends Application {
}

Jersey adds ResourceConfig, which extends/configures the application model:

import org.glassfish.jersey.server.ResourceConfig;

public class QuoteApplication extends ResourceConfig {
  public QuoteApplication() {
    packages("com.example.quote.api");
    register(QuoteExceptionMapper.class);
  }
}

Comparison:

ConcernApplicationResourceConfig
SourceJakarta REST standardJersey-specific
PurposeDefines applicationRich Jersey configuration
Explicit registrationpossiblecommon and ergonomic
Package scanningnot the same standardized behaviorJersey-supported style
Jersey propertiesno direct modelcommon place to configure
Portabilityhigherlower
Practicality in Jersey appsbasichigh

Senior review rule:

Using ResourceConfig is acceptable if Jersey is the intended implementation.
But do not treat ResourceConfig behavior as Jakarta REST standard behavior.

6. Standard Feature vs Jersey Feature Usage

Jakarta REST defines a standard feature extension point:

import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;

public class AuditFeature implements Feature {
  @Override
  public boolean configure(FeatureContext context) {
    context.register(AuditRequestFilter.class);
    return true;
  }
}

This interface is standard.

But many feature classes you register may be implementation/library-specific:

register(JacksonFeature.class);       // Jersey/Jackson module
register(MultiPartFeature.class);     // Jersey multipart module
register(LoggingFeature.class);       // Jersey logging module

So the question is not only:

Is Feature standard?

The better question is:

Which concrete Feature is being registered, and which runtime provides it?

7. Dependency Injection Boundary

JAX-RS has resource classes and providers.

But object creation/injection can come from different mechanisms:

Manual construction
Jakarta REST runtime construction
Jersey HK2 service locator
CDI container
Spring bridge, if present
Custom factory/binder

Jersey commonly uses HK2 internally and can integrate with CDI.

Examples of different layers:

import jakarta.inject.Inject;                // Jakarta Inject standard-ish annotation
import jakarta.enterprise.context.RequestScoped; // CDI
import org.glassfish.hk2.api.Factory;        // HK2
import org.glassfish.jersey.internal.inject.AbstractBinder; // Jersey/HK2 binding style

Important nuance:

The annotation @Inject alone does not prove which DI container owns lifecycle.

You need to verify:

  • who creates the resource object,
  • who creates the dependency,
  • which scopes are active,
  • whether HK2 and CDI are bridged,
  • whether tests use a different injection path than production.

Example smell:

@Path("/orders")
public class OrderResource {
  @Inject
  OrderService orderService;
}

Questions:

Is @Inject resolved by HK2?
Is it resolved by CDI?
Is OrderService registered as a binder?
Is OrderService a CDI bean?
Does this work in integration tests and production the same way?

This becomes critical for startup failures and lifecycle bugs.


8. Provider Discovery Boundary

JAX-RS has @Provider:

@Provider
public class QuoteExceptionMapper implements ExceptionMapper<QuoteException> {
  ...
}

But how the provider is discovered depends on registration/runtime:

Explicit registration
Package scanning
Classpath scanning
Auto-discovery
META-INF/services
Container integration

Standard concept:

Provider participates in JAX-RS runtime.

Implementation-specific concern:

How does the runtime discover and prioritize the provider?

Jersey apps often use a mix:

public class QuoteApplication extends ResourceConfig {
  public QuoteApplication() {
    packages("com.example.quote.api");
    register(QuoteExceptionMapper.class);
    register(JacksonFeature.class);
  }
}

The more discovery mechanisms are mixed, the harder startup behavior becomes to reason about.

Senior review preference:

For critical providers, prefer explicit registration or clearly documented scanning boundaries.

Critical providers include:

  • authentication filters,
  • authorization filters,
  • exception mappers,
  • JSON providers/custom mappers,
  • validation mappers,
  • correlation/tracing filters,
  • request/response logging filters,
  • multi-tenancy resolvers.

9. Media Modules: Standard Concept, Jersey Implementation

Jakarta REST supports entity providers.

But concrete JSON/XML/multipart support comes from installed modules.

Examples:

Standard concept:
  MessageBodyReader / MessageBodyWriter
  @Consumes / @Produces
  MediaType

Implementation/library modules:
  Jersey Jackson module
  Jersey JSON-B module
  Jersey JAXB support
  Jersey multipart module
  Custom internal provider

Common dependency clues:

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-jackson</artifactId>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-binding</artifactId>
</dependency>

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-multipart</artifactId>
</dependency>

Do not infer serialization behavior from DTO code alone.

You must inspect:

  • media module dependency,
  • feature registration,
  • object mapper config,
  • context resolver,
  • provider priority,
  • classpath conflicts,
  • test runtime versus production runtime.

10. Runtime Boundary: Servlet vs Standalone

JAX-RS can run in different runtime models.

Two common patterns:

Servlet container model:
  Client -> Gateway -> Servlet container -> Jersey servlet/filter -> JAX-RS resource

Standalone embedded model:
  Client -> Gateway -> Embedded HTTP server -> Jersey runtime -> JAX-RS resource

Servlet model might involve:

  • Tomcat,
  • Jetty,
  • GlassFish servlet runtime,
  • ServletContainer,
  • servlet filters,
  • context path,
  • WAR deployment.

Standalone model might involve:

  • embedded Grizzly,
  • main method launcher,
  • fat jar,
  • direct HTTP server setup.

The same resource class can behave differently operationally because the runtime envelope differs:

ConcernServlet containerEmbedded runtime
Context pathcontainer/webapp controlledapp/server controlled
Filter chainservlet filters + JAX-RS filtersmostly app/runtime filters
Deployment artifactWAR or container imageexecutable jar/container
Thread poolcontainer-managedapp/runtime-managed
TLSoften external/container/gatewayapp or gateway
Graceful shutdowncontainer/platform integrationapp must handle explicitly
Metrics/loggingcontainer hooks possibleapp hooks required

Internal verification is mandatory.


11. Import-Based Reading Cheat Sheet

When opening unfamiliar code, start with imports.

Import prefixLikely meaning
jakarta.ws.rs.*Jakarta REST standard API
jakarta.ws.rs.core.*Standard request/response/context types
jakarta.ws.rs.ext.*Standard provider extension point
org.glassfish.jersey.*Jersey-specific implementation/config/module
org.glassfish.hk2.*HK2 service locator / DI infrastructure
jakarta.inject.*Injection annotation API
jakarta.enterprise.*CDI concepts
jakarta.servlet.*Servlet container concepts
com.fasterxml.jackson.*Jackson serialization
jakarta.json.bind.*JSON-B serialization
jakarta.validation.*Bean Validation API

Example quick classification:

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.jackson.JacksonFeature;

Read as:

Resource method model:
  Jakarta REST standard

Application bootstrap and JSON provider registration:
  Jersey-specific

12. Dependency-Based Verification Cheat Sheet

Look at pom.xml.

Typical indicators:

<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>

Means:

The standard API is available.
It does not prove the runtime implementation.

Jersey indicators:

<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>

Media indicators:

<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>

CDI indicators may include CDI API/runtime or Jersey CDI integration modules.

But dependency alone is not proof of active runtime behavior.

You still need to inspect:

  • application bootstrap,
  • deployment descriptor,
  • main class,
  • container integration,
  • runtime logs,
  • production image contents,
  • startup command.

13. Portability Matrix

Not all code has the same portability.

CodePortabilityReview implication
@Path, @GET, @ProduceshighStandard JAX-RS behavior
Response.status(404)highStandard response model
ExceptionMapper<T>high conceptuallyDiscovery/ordering may vary
MessageBodyReader<T>high conceptuallyRegistration/provider choice may vary
ResourceConfigJersey-specificVerify Jersey runtime
JacksonFeatureJersey/Jackson-specificVerify module and object mapper config
AbstractBinderJersey/HK2-specificVerify HK2 injection model
ServletContext injectionServlet-specificVerify servlet runtime
Grizzly bootstrap codeGrizzly-specificVerify embedded runtime
CDI qualifiers/scopesCDI-specificVerify CDI active and bridged

Portability is not always a goal.

Production clarity is the goal.

A Jersey-specific implementation is fine when:

  • the team intentionally standardized on Jersey,
  • the behavior is documented,
  • tests run against the same mechanism,
  • operational dashboards/logs expose startup/runtime failures,
  • PR reviewers know what is standard and what is not.

14. Common Misreadings

Misreading 1: “It imports jakarta.ws.rs.*, so it is Jakarta EE.”

Not necessarily.

A service can use Jakarta REST APIs without being deployed to a full Jakarta EE application server.

It may run as:

Jersey + embedded Grizzly
Jersey + Tomcat
Jersey + Jetty
Jersey + custom platform wrapper

Misreading 2: “@Inject means CDI.”

Not necessarily.

@Inject can be resolved in different ways depending on runtime integration.

Misreading 3: “@Provider means it is automatically active.”

Not necessarily.

Provider discovery depends on scanning/registration/autodiscovery configuration.

Misreading 4: “Jackson is used because DTOs look like JSON DTOs.”

Not necessarily.

You need to verify provider dependency and registration.

Misreading 5: “Servlet filters and JAX-RS filters are the same.”

No.

Servlet filters run at the servlet container layer. JAX-RS filters run inside the JAX-RS runtime pipeline.


15. Debugging by Layer

When a bug appears, classify it by layer before changing code.

flowchart TD A[Symptom] --> B{Which layer?} B --> C[HTTP/Gateway] B --> D[Container] B --> E[Jersey Runtime] B --> F[Jakarta REST Resource Model] B --> G[Application Code] B --> H[Internal Platform] C --> C1[Headers, path rewrite, timeout, TLS] D --> D1[Servlet mapping, filter chain, thread pool] E --> E1[ResourceConfig, provider registration, HK2] F --> F1[@Path, @Consumes, @Produces, matching] G --> G1[Business logic, DTO mapping, service call] H --> H1[Deployment, config, secret, observability]

Example symptom: resource method not invoked.

Check sequence:

  1. Did request reach the pod/process?
  2. Did container route to Jersey servlet/runtime?
  3. Did base path/context path match?
  4. Did JAX-RS path and method match?
  5. Did @Consumes match Content-Type?
  6. Did entity provider deserialize body?
  7. Did filter abort the request?
  8. Did authentication/authorization reject it?
  9. Did exception mapper hide the real cause?

This order prevents random debugging.


16. CSG Quote & Order Context — Safe Interpretation

For CSG Quote & Order style systems, the likely architectural pressure includes:

  • enterprise HTTP APIs,
  • complex DTO contracts,
  • quote/order lifecycle operations,
  • integration with catalog/pricing/order subsystems,
  • database-backed business state,
  • event-driven coordination,
  • cloud/on-prem deployment variation,
  • strict operational and compatibility requirements.

But do not infer internal implementation from product domain.

Safe statement:

A Quote & Order backend may use JAX-RS/Jersey-like patterns for HTTP APIs.
The actual runtime, DI container, provider set, deployment model, and platform conventions must be verified internally.

Unsafe statement:

CSG Quote & Order uses embedded Grizzly with HK2 and JacksonFeature.

Unless proven by code or internal docs, that is speculation.


17. Internal Verification Checklist

Use this checklist when onboarding into an unfamiliar JAX-RS/Jersey codebase.

Dependency verification

  • Is jakarta.ws.rs-api present?
  • Is jersey-server present?
  • Is jersey-container-servlet-* present?
  • Is jersey-container-grizzly2-http present?
  • Is jersey-hk2 present?
  • Is a CDI implementation/integration present?
  • Which JSON module is present: Jackson, JSON-B, both, or custom?
  • Is multipart support present?
  • Are versions centrally managed by BOM/parent POM?

Bootstrap verification

  • Is there an Application subclass?
  • Is there a ResourceConfig subclass?
  • Is bootstrap done via web.xml, servlet initializer, main method, or platform wrapper?
  • Are resources registered explicitly or via package scanning?
  • Are providers registered explicitly or discovered?
  • Are features registered manually?
  • Are any Jersey properties set?

Runtime/container verification

  • Does service run on Tomcat, Jetty, GlassFish, Grizzly, or something else?
  • Is it WAR, executable JAR, or containerized process?
  • Where is context path defined?
  • Where is base API path defined?
  • What owns thread pools?
  • How is graceful shutdown handled?

DI verification

  • Is HK2 used directly?
  • Is CDI used?
  • Is @Inject resolved consistently in tests and production?
  • Are resources request-scoped or singleton?
  • Are providers singleton?
  • Are mutable dependencies safe?

Provider verification

  • Which exception mappers exist?
  • Which filters run globally?
  • Which JSON provider is active?
  • Is object mapper customized?
  • Are multiple providers competing?
  • Are priorities explicit?

Production verification

  • Are startup logs enough to see registered resources/providers?
  • Is there a health endpoint?
  • Is there a metrics endpoint?
  • Is request tracing/correlation present?
  • Are config values visible in sanitized startup diagnostics?
  • Is there a runbook for 404/405/415/500 startup/runtime issues?

18. PR Review Checklist

When reviewing a change involving JAX-RS/Jersey runtime behavior, ask:

Standard vs implementation

  • Is this using Jakarta REST standard API or Jersey-specific API?
  • Is Jersey-specific usage intentional?
  • Would this behavior change if implementation changes?
  • Is portability required or irrelevant?

Registration

  • Is new resource/provider registered?
  • Is registration explicit or scanning-based?
  • Could scanning accidentally register too much?
  • Could provider order change existing behavior?

Serialization

  • Does the change depend on Jackson/JSON-B/JAXB config?
  • Are date/time, enum, BigDecimal, null, and unknown field behavior clear?
  • Are tests using the real provider?

DI/lifecycle

  • Who creates the object?
  • Which scope owns it?
  • Is mutable state safe?
  • Does injection work in production and tests?

Runtime

  • Does the change assume servlet-specific behavior?
  • Does it assume Grizzly/GlassFish-specific behavior?
  • Does it depend on path/context behavior controlled by deployment?

Observability

  • If this fails at startup, will logs identify the failing registration?
  • If this fails at request time, will logs/traces show matching/provider/filter failure?

19. Failure Modes

FailureLikely causeLayer
Resource not foundpath mismatch, context path, package scanning missingstandard/runtime/Jersey
405method mismatchstandard
406Accept vs @Produces mismatchstandard/provider
415Content-Type vs @Consumes or missing readerstandard/provider/Jersey
Startup failureduplicate provider, injection failure, invalid resource modelJersey/DI
@Inject null/failsmissing binder, CDI not active, scope issueDI/Jersey/CDI
JSON behavior changedprovider/object mapper config changedmedia provider
Exception not mappedmapper not registered or wrong type priorityprovider/Jersey
Auth filter not executednot registered or wrong layer/filter typeJersey/Servlet/platform
Works locally, fails in proddifferent runtime/config/classpathplatform

20. Senior Mental Model

A JAX-RS service is not just this:

Annotation -> method call

It is this:

HTTP runtime
  -> container mapping
  -> implementation bootstrap
  -> resource model
  -> provider registry
  -> dependency injection
  -> filter/interceptor chain
  -> entity conversion
  -> resource method
  -> response conversion
  -> platform observability

Jakarta REST gives the standard vocabulary.

Jersey gives one concrete implementation.

The internal platform gives the real production behavior.

Senior engineers must keep all three visible.


21. Key Takeaways

  • Jakarta REST/JAX-RS is the standard programming model.
  • Jersey is an implementation with useful but non-portable behavior.
  • jakarta.ws.rs.* usually means standard API.
  • org.glassfish.jersey.* means Jersey-specific implementation/configuration.
  • ResourceConfig is Jersey-specific.
  • Application is standard.
  • @Inject does not prove CDI; verify DI ownership.
  • @Provider does not prove activation; verify discovery/registration.
  • Serialization behavior depends on actual provider modules and configuration.
  • Runtime behavior depends on Servlet/GlassFish/Grizzly/Tomcat/Jetty/platform deployment.
  • For CSG internal systems, verify from code, deployment, logs, and team docs; do not infer.
Lesson Recap

You just completed lesson 13 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.