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
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:
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:
| Concern | Standard Jakarta REST? | Jersey-specific? | Runtime/platform-specific? |
|---|---|---|---|
@Path, @GET, @POST | yes | no | no |
| Request matching rules | mostly yes | edge behavior may vary | proxy path may affect |
MessageBodyReader/Writer | yes | provider registration varies | classpath/module config matters |
ResourceConfig | no | yes | no |
| HK2 binding | no | yes/Jersey ecosystem | no |
| CDI integration | Jakarta CDI standard, integration-specific | Jersey integration-specific | container-specific |
| Servlet filter chain | no | no | Servlet container |
| Embedded Grizzly server | no | Jersey/Grizzly usage | runtime-specific |
| Auto-discovery behavior | no | often Jersey-specific | classpath-specific |
| Startup error messages | no | yes | deployment-specific |
| Production timeout behavior | no | partly | mostly 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
Featureusage 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:
| Concern | Application | ResourceConfig |
|---|---|---|
| Source | Jakarta REST standard | Jersey-specific |
| Purpose | Defines application | Rich Jersey configuration |
| Explicit registration | possible | common and ergonomic |
| Package scanning | not the same standardized behavior | Jersey-supported style |
| Jersey properties | no direct model | common place to configure |
| Portability | higher | lower |
| Practicality in Jersey apps | basic | high |
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:
| Concern | Servlet container | Embedded runtime |
|---|---|---|
| Context path | container/webapp controlled | app/server controlled |
| Filter chain | servlet filters + JAX-RS filters | mostly app/runtime filters |
| Deployment artifact | WAR or container image | executable jar/container |
| Thread pool | container-managed | app/runtime-managed |
| TLS | often external/container/gateway | app or gateway |
| Graceful shutdown | container/platform integration | app must handle explicitly |
| Metrics/logging | container hooks possible | app hooks required |
Internal verification is mandatory.
11. Import-Based Reading Cheat Sheet
When opening unfamiliar code, start with imports.
| Import prefix | Likely 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.
| Code | Portability | Review implication |
|---|---|---|
@Path, @GET, @Produces | high | Standard JAX-RS behavior |
Response.status(404) | high | Standard response model |
ExceptionMapper<T> | high conceptually | Discovery/ordering may vary |
MessageBodyReader<T> | high conceptually | Registration/provider choice may vary |
ResourceConfig | Jersey-specific | Verify Jersey runtime |
JacksonFeature | Jersey/Jackson-specific | Verify module and object mapper config |
AbstractBinder | Jersey/HK2-specific | Verify HK2 injection model |
ServletContext injection | Servlet-specific | Verify servlet runtime |
| Grizzly bootstrap code | Grizzly-specific | Verify embedded runtime |
| CDI qualifiers/scopes | CDI-specific | Verify 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.
Example symptom: resource method not invoked.
Check sequence:
- Did request reach the pod/process?
- Did container route to Jersey servlet/runtime?
- Did base path/context path match?
- Did JAX-RS path and method match?
- Did
@ConsumesmatchContent-Type? - Did entity provider deserialize body?
- Did filter abort the request?
- Did authentication/authorization reject it?
- 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-apipresent? - Is
jersey-serverpresent? - Is
jersey-container-servlet-*present? - Is
jersey-container-grizzly2-httppresent? - Is
jersey-hk2present? - 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
Applicationsubclass? - Is there a
ResourceConfigsubclass? - 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
@Injectresolved 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
| Failure | Likely cause | Layer |
|---|---|---|
| Resource not found | path mismatch, context path, package scanning missing | standard/runtime/Jersey |
| 405 | method mismatch | standard |
| 406 | Accept vs @Produces mismatch | standard/provider |
| 415 | Content-Type vs @Consumes or missing reader | standard/provider/Jersey |
| Startup failure | duplicate provider, injection failure, invalid resource model | Jersey/DI |
@Inject null/fails | missing binder, CDI not active, scope issue | DI/Jersey/CDI |
| JSON behavior changed | provider/object mapper config changed | media provider |
| Exception not mapped | mapper not registered or wrong type priority | provider/Jersey |
| Auth filter not executed | not registered or wrong layer/filter type | Jersey/Servlet/platform |
| Works locally, fails in prod | different runtime/config/classpath | platform |
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.ResourceConfigis Jersey-specific.Applicationis standard.@Injectdoes not prove CDI; verify DI ownership.@Providerdoes 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.
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.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.