JAX-RS compatibility
View MarkdownVertique’s REST layer is authored entirely with JAX-RS 4.0 annotations (jakarta.ws.rs-api
4.0.0), but the runtime underneath is Vertique’s own Vert.x-native engine. It is not Jersey,
RESTEasy, or any other container. JAX-RS is the authoring surface, a familiar and specified
vocabulary for describing an HTTP resource, and nothing underneath expects a full JAX-RS
container. The surface therefore splits three ways:
- Supported. Behaves as the specification says: resources and verbs, parameter binding,
ExceptionMapper,Response. - Deliberately replaced. A Vert.x-shaped equivalent exists: filters and providers become
Dagger-wired interceptors and codecs,
@Suspendedbecomes returning a future, and the JAX-RS client becomes the declarative REST client. - Not implemented. For each of these, this page states what happens if you use it anyway.
The table below summarizes each area, and each section then gives the behavior in detail. Everything applies the same way whether a resource is discovered by the reflective scanner at startup or compiled ahead of time by the annotation processor. The two paths are built to match exactly.
At a glance
Section titled “At a glance”| Area | Verdict | Detail |
|---|---|---|
| Resources and methods | Supported, with one gap | HTTP-verb annotations, @Produces/@Consumes, and regex-constrained @Path templates all work; sub-resource locators do not exist. |
| Parameters | Supported, with one gap | Every standard parameter-binding annotation except @MatrixParam works on a resource method, and a real ParamConverterProvider is consulted for scalar conversion. |
| Bodies and entities | Diverges | Body reading and writing run through a framework decoder/encoder SPI, not MessageBodyReader/MessageBodyWriter; multipart EntityPart is supported within that model. |
| Responses | Supported, with gaps | Response and @Produces negotiation work as specified, though the builder’s variant methods throw; StreamingOutput is not recognized; there is no response-body contract validation. |
| Providers and filters | Diverges | ContainerRequestFilter, ContainerResponseFilter, ReaderInterceptor, WriterInterceptor, @Provider, and @NameBinding are all replaced by framework interceptor and codec extension points wired through Dagger. |
| Exception handling | Supported | ExceptionMapper and WebApplicationException behave as specified, backed by a hierarchy-aware registry. |
| Context injection | Supported, with a reserved list | @Context resolves the Vert.x routing context, the JAX-RS SecurityContext, and framework context types; seven standard JAX-RS context types are rejected at startup. |
| Validation | Diverges by default | The default request-validation path enforces Bean Validation annotations as a synthesized JSON Schema, not as Jakarta Validation; a genuine Bean Validation runtime is available as a separate opt-in. |
| Async model | Diverges | A resource method returns a Future; @Suspended and AsyncResponse are never recognized. |
| Resilience on a resource | Vertique extension | @Resilient and @Timeout bound a concrete Dagger-managed resource method; neither is part of standard JAX-RS. |
| Client API | Not supported | jakarta.ws.rs.client is not implemented; a separate declarative REST client module is the framework’s alternative for calling other HTTP services. |
Resources and methods
Section titled “Resources and methods”Every standard HTTP-verb annotation is supported: @GET, @POST, @PUT, @DELETE, @PATCH,
@HEAD, and @OPTIONS all route through the same scan, whether it runs reflectively or at compile
time. @HEAD additionally has its entity stripped from the response after conditional-request
evaluation, matching HTTP semantics. @Produces and @Consumes are read at both class and method
level (method overrides class) and are enforced. An incoming request whose Content-Type does not
satisfy the operation’s @Consumes is rejected with 415 before validation or the resource method
runs, and @Produces drives Accept-header content negotiation on the way out (see
Responses below). The annotation is read with one divergence: each array element
must hold exactly one media type. A comma-separated list inside a single string is not split and
is treated as one invalid media type. Declare multiple media types as separate array elements.
A plain @Path template such as /items/{id} is translated into a Vert.x route with a named path
parameter. A regex-constrained template such as /items/{id:[0-9]+} is supported too: the whole
template compiles to a single anchored regular expression with one named capture group per
variable, so path-parameter binding by name works the same way.
Sub-resource locators are not supported. A sub-resource locator is a method that carries @Path
but no HTTP-verb annotation and returns another resource instance for further dispatch. A method
with zero verb annotations is treated as a non-endpoint method and silently skipped: no route is
registered for it, and nothing is logged or reported at startup. A resource method written as a
sub-resource locator never receives a request, and there is no compile-time or startup signal that
it was ignored.
Parameters
Section titled “Parameters”Every JAX-RS parameter-binding annotation except one is supported on a resource method:
@PathParam, @QueryParam, @HeaderParam, @CookieParam, @FormParam, and @BeanParam all bind
as specified, with @DefaultValue honored on every scalar form. @MatrixParam is the exception.
It is recognized only in the declarative REST client’s request interfaces (see
Client API) and in the framework’s UriBuilder implementation for constructing
URIs. It is not recognized on a server resource method. A @MatrixParam-annotated resource-method
parameter is not rejected: the parameter scanner does not recognize the annotation, so the
parameter falls through the same path an unannotated parameter takes and is bound as the request
body. If the method already declares a real, unannotated body parameter, that combination fails at
startup over the duplicate body binding. Otherwise the framework silently tries to deserialize the
request body into whatever type the @MatrixParam parameter declared.
Scalar type conversion is layered. A built-in native converter registry covers String, the
primitive and boxed numeric types, boolean, char, BigInteger, BigDecimal, UUID, URI,
every java.time value type, and any enum (converted by exact constant name). Beyond that
registry, a standard jakarta.ws.rs.ext.ParamConverterProvider contributed by the application is
consulted at request time, in ascending @Priority order, and the first non-null result wins. A
parameter type that neither the registry nor any registered provider can satisfy is rejected at
startup, before the application serves a request.
Bodies and entities
Section titled “Bodies and entities”Request-body reading and response-body writing run entirely through two framework SPIs: a decoder
chain for reading and an encoder chain for writing. JAX-RS’s own
MessageBodyReader/MessageBodyWriter contracts are not part of the model. Nothing in the
framework looks for one, and contributing one has no effect.
The decoder chain ships four built-in decoders, tried in priority order. The first whose declared Content-Type and target type both match wins. The four are:
- a text decoder for any
text/*body into aString; - a binary decoder for
application/octet-streaminto aBufferor abyte[]; - a form decoder for
application/x-www-form-urlencodedinto any plain object; - a JSON decoder as the fallback for everything else, including a raw
JsonObject.
An application-contributed decoder runs before all four by default, so it can intercept a content type the framework would otherwise handle.
Multipart is supported, including the JAX-RS EntityPart representation alongside the plain
Vert.x file-upload type. A resource method can declare a named EntityPart parameter, a named list
of them, or an unannotated aggregate list covering every part of the request, both file-backed
parts and text-field parts. This support has one limit: the framework’s own file-part
size/content-type constraint annotation is accepted only on the plain file-upload type and not on
an EntityPart parameter. An EntityPart may represent a text field the constraint mechanism
cannot see, so accepting the annotation there would open a bypass.
Streaming applies to responses only. Before any decoder or your resource method runs, the router’s body handler always reads the request body fully and, for multipart, spools it to disk. There is no way to receive a live, unbuffered inbound stream. A response can stream: a resource method may return a live byte stream that is piped to the client as it produces data, and a dedicated mechanism is available for Server-Sent Events.
Responses
Section titled “Responses”jakarta.ws.rs.core.Response is a first-class return type, built the normal way and returned
either directly or wrapped in a Future. Because the runtime is not built on Jersey or RESTEasy,
vertique-rest-jaxrs ships its own minimal RuntimeDelegate. It provides just enough for the
standard Response builder, UriBuilder, and Link to work standalone, and anything a full
JAX-RS container would additionally provide is out of scope. The builder itself has one limit: the
variants(...) methods throw UnsupportedOperationException, so variant-based negotiation cannot
be expressed through the builder.
@Produces content negotiation works against the request’s Accept header. Candidate media types
come from the operation’s @Produces list, or a JSON default when none is declared, and are
matched by specificity and quality value. An unsatisfiable request is answered with 406, the same
outcome a compliant JAX-RS container would produce. This negotiation is media-type only, with no
charset or language negotiation layered onto it.
Two things a JAX-RS-literate reader might expect are missing. First, StreamingOutput is not
implemented. The equivalent is returning a live stream directly from the resource method, as
described above. Second, neither of the framework’s request-validation strategies validates a
resource method’s response against any schema or contract. Both gate the request only. A response
body that violates its own declared shape is not caught by the framework, and an application that
needs that guarantee has to check it itself.
Providers and filters
Section titled “Providers and filters”None of the classic JAX-RS provider or filter contracts are implemented: ContainerRequestFilter,
ContainerResponseFilter, ReaderInterceptor, WriterInterceptor, the @Provider registration
annotation, and @NameBinding do not exist anywhere in the runtime. The divergence is deliberate:
the framework replaces the whole family with its own Dagger-wired interceptor and codec extension
points, detailed in full in the rest-core module reference below. The rough correspondence, for a
reader coming from standard JAX-RS:
| Instead of | Reach for |
|---|---|
ContainerRequestFilter / ContainerResponseFilter |
a request interceptor, contributed through a Dagger multibinding, with hooks that run before dispatch and after the response is produced |
ReaderInterceptor |
the request body decoder chain described under Bodies and entities |
WriterInterceptor |
the response body encoder chain |
@Provider |
a Dagger multibinding contribution. Registration happens at compile time, not by classpath scanning |
@NameBinding |
nothing declarative; a per-operation interceptor inspects the operation’s own metadata to decide whether to act, or a handler contributor is placed at a specific priority range instead |
An additional per-operation interceptor is also available. It runs once per matched resource-method invocation instead of once per request at the router level, and it has its own before, after, and recover hooks around the method call. It is the closest match to a JAX-RS filter scoped to one resource method, but it is contributed the same way, through a Dagger multibinding. It does not use a name-binding annotation.
Exception handling
Section titled “Exception handling”jakarta.ws.rs.ext.ExceptionMapper is supported as specified: implement it for your exception
type and contribute it through a Dagger multibinding, and it is consulted ahead of the framework’s
own defaults for that same type. Lookup is hierarchy-aware (a mapper registered for a superclass
still matches a thrown subclass), and results are cached after the first lookup.
WebApplicationException behaves as specified too: its carried Response is used directly, with
the original entity preserved when present. A long list of the framework’s own exception types
already has a mapper contributed out of the box, including validation failures, not-found,
conflict, unauthorized, forbidden, unavailable, and a few more. An uncaught, unmapped throwable of
any other type still gets a well-formed problem-details response, so the client does not see a raw
stack trace or a hung connection.
Context injection
Section titled “Context injection”@Context resolves a fixed, framework-chosen set of types and does not resolve every type a JAX-RS
container might supply. The set is the Vert.x routing context, the JAX-RS SecurityContext (the
exact interface only, not a custom subtype), and any of the framework’s own context-value types:
its own security context, correlation data, localization, and similar request-scoped application
values. All of them resolve through the same ordered resolver chain, and an application can
contribute its own resolver for a custom context-value type.
Seven standard JAX-RS context types are explicitly not supported: UriInfo, HttpHeaders,
Request, Configuration, Application, Providers, and ResourceContext. These are recognized
as syntactically valid @Context targets, but a resource method declaring one of them fails at
startup with a clear error. It does not start successfully and then inject a null value the first
time the method runs. The same fail-fast treatment applies to a @Context parameter combined with
a JAX-RS value-binding annotation on the same parameter, and to a @Context parameter whose
declared type is neither a built-in injectable type nor an application context-value type. Both
are startup errors.
Validation
Section titled “Validation”Bean Validation annotations on a request body or parameter are read by default, but they are not
enforced by a Jakarta Validation (JSR 380) engine. The default request-validation path reads the
same annotations JAX-RS and Bean Validation put on a resource method and its request types and
synthesizes a JSON Schema fragment per operation from them, once, at startup. Every request is then
validated against that synthesized schema, and the original annotations are not evaluated at
request time. Most of the time the two enforcement models agree, but they are not identical, and
the difference is observable. JSON Schema’s pattern keyword is an unanchored search that matches
if the pattern is found anywhere in the value, while a real Bean Validation @Pattern match is
fully anchored against the whole value. A constraint requiring at least one non-whitespace
character, for example, behaves like a not-blank check under the framework’s default
JSON-Schema-backed path, but would reject any longer value outright under a genuine Bean Validation
runtime.
A genuine Jakarta Validation runtime is also available as a separate, opt-in module. Once it is included in the application’s dependency-injection graph, it runs constraint validation with Bean Validation semantics against resource-method arguments, after parameters are extracted and before the resource method is invoked. The two mechanisms are independent and can both be active on the same application at once: the schema gate runs first as part of request validation, and the opt-in Bean Validation runtime runs second as part of method dispatch. Response bodies are validated by neither mechanism.
See REST APIs for how the default schema-based path is wired and configured, and
the vertique-validation module reference below for the separate Bean Validation runtime.
Resilience on a resource
Section titled “Resilience on a resource”@Resilient is a Vertique AOP annotation and is not part of standard JAX-RS. For an inbound
resource, the documented use is to put it on a concrete Dagger-managed resource method together
with @Timeout. The generated proxy then bounds the asynchronous execution. This page does not
recommend retry, circuit-breaker, or bulkhead policy at the inbound resource boundary. Those
policies belong on the outbound client or service operation that owns the corresponding failure
and replay semantics.
JAX-RS routing and AOP have separate responsibilities. The route performs authentication,
content-type checks, and request validation before invoking the resource. The AOP proxy wraps the
resource method and its downstream asynchronous work. To enable the path, include the generated
GeneratedAopModule and GeneratedJaxRsResourcesModule together with ResilienceModule and
ResilienceAopModule in the component. The
REST APIs timeout section shows the
resource-side setup.
Async model
Section titled “Async model”A resource method’s return type is Future. A synchronous return works too, but every
non-trivial resource method that does I/O returns a Future of its result, resolved asynchronously
before the response is serialized. A Future that completes with no value sends 204 with no body,
matching a void method. That is the only asynchronous model available. The standard JAX-RS
asynchronous pattern, a @Suspended AsyncResponse parameter that the method resumes later from
another thread, is not implemented. Both the annotation and the type ship with the Jakarta WS-RS
API dependency, so the declaration compiles, but no code path recognizes either, and the parameter
falls through the same unannotated-parameter body binding described under
Parameters. On a Vert.x-native runtime, a Future-returning method already
expresses the same intent without a second, parallel mechanism.
Application classes
Section titled “Application classes”A standard Jakarta REST Application subclass is not supported as a way to group resources: it is
not registered, and nothing in it takes effect. A named application is declared with
@RestApplication on an interface. See Several APIs for how
declaring one changes mounting.
| Concern | Verdict | Detail |
|---|---|---|
Application subclass |
Not registered | The class compiles with one build warning naming it, which a build that treats warnings as errors turns into a failure; @NoAutoWire on the class silences the warning. No route ever answers under it. |
@ApplicationPath |
No effect | Nothing is mounted at its value. |
getClasses() |
No effect | Its return value selects nothing. |
getSingletons() |
No effect | Its return value selects nothing. |
| Its resources | Served elsewhere | At jaxrs.basePath on the legacy default mount when no @RestApplication is declared, otherwise only where a declared application selects them. |
| Named applications | Vertique replacement | A @RestApplication interface names the application, its path, and either its listed resources or a sole discover = true; the interface is never implemented or instantiated. |
Porting a subclass keeps its path: declare a @RestApplication interface with the same path that
lists the subclass’s resources, or sets discover = true when it is the sole declaration, then
delete the subclass. Until then, rules keyed on the old @ApplicationPath prefix, such as gateway
routes and allowlists, stop matching its resources.
See the @RestApplication section of the vertique-rest-jaxrs module reference
for membership, lifecycle, and mount-conflict rules, and the vertique-codegen-jaxrs module
reference’s
porting section
for a before-and-after port and the build warning’s exact text.
Client API
Section titled “Client API”jakarta.ws.rs.client (Client, ClientBuilder, WebTarget, and the rest of the JAX-RS client
API) is not implemented and is not the framework’s model for calling another HTTP service. The
alternative is a separate, declarative REST client: annotate a plain Java interface with
@RestClient plus the same JAX-RS annotations used on a server resource method (@GET, @Path,
@QueryParam, and so on), build it once through the client factory, and call its methods like any
other injected service. Each call becomes an asynchronous HTTP request over a Vert.x-backed web
client and shares the same parameter-conversion stack the server side uses. This is an outbound
client feature and does not provide JAX-RS client compatibility. See REST clients
for its annotation surface, resilience, configuration, and request-execution pipeline.
Learn more
Section titled “Learn more”vertique-rest-jaxrsmodule reference: the routing runtime, parameter-extraction table, and return-type handling this page describes.vertique-rest-coremodule reference: the interceptor, decoder/encoder, and context-resolver extension points that replace the JAX-RS provider and filter contracts.vertique-rest-validationmodule reference: the default JSON-Schema-synthesis request-validation strategy.vertique-rest-openapi-validationmodule reference: the opt-in contract-validation strategy.vertique-validationmodule reference: the separate Jakarta Bean Validation runtime.vertique-rest-clientmodule reference: the declarative REST client named above as thejakarta.ws.rs.clientalternative.- PostgreSQL REST archetype
CreateItemRequest: the real@Pattern/@NotBlankcombination behind the JSON-Schema-vs-Bean-Validation example above. - Quickstart
- REST APIs
- Services: define a typed service contract and inject its generated client.
- What is Vertique?
Continue reading
Section titled “Continue reading”- Previous: REST clients
- Next: Services