REST Server — Mixin Sub-Contexts
@Rest(mixins=...) got a structural upgrade in 10.0.0. The user-facing API is unchanged
(@Rest(mixins=Foo.class, Bar.class) still grafts the listed classes' @RestOp methods into the
importing resource's operation tree), but the implementation now elevates each mixin from a
"method library absorbed into the host's RestContext" to an "embedded sub-resource with its own
RestContext parent-linked to the host's."
Pre-10.0.0, mixin endpoints resolved serializers, parsers, guards, hooks, etc. through the host's
single RestContext. There was no place for mixin-scoped configuration — the mixin's
class-level @Rest(serializers=..., guards=..., ...) declarations were silently ignored. Starting
in 10.0.0, each mixin owns its own RestContext whose parentContext points at the host's, and
inheritance walks parent → mixin per property. The mixin can append to or replace each
contribution list, and the framework's @Rest(noInherit={...}) opt-out blocks inheritance per
property.
This is the opposite design choice from
@Rest(children=...). Children are deliberately isolated from the parent's resolution chain. Mixins inherit. The divergence is intentional — see Mixin-vs-child divergence below.
This topic covers the inheritance model, the noInherit opt-out, hook dual-firing, the
flat-inheritance rule for transitive mixins, per-mixin overrides, the new
Messages.chain(...) seam, and the guard inheritance security default.
What changed and why
Pre-10.0.0 mixins were "method libraries absorbed into the host's RestContext." Mixin classes
that declared @Rest(serializers=..., guards=..., hooks=..., callLogger=..., ...) got those
contributions silently ignored — every mixin endpoint resolved through the host's resolver chain
with no place for mixin-scoped overrides. 10.0.0 promotes each mixin to "an embedded sub-resource,
each with their own RestContext parent-linked to the host." Mixin endpoints now resolve through
the mixin's RestContext, which inherits the host's contributions (parent walk) and optionally
appends or replaces them.
The rationale behind this design:
"Children are completely independent from parents — they're externally-mounted at their own URL namespace, often have their own deployment lifecycle (factored into separate jars, etc.), and conflating their resolution with the host's would surprise authors who treat children as standalone REST endpoints that happen to be discovered through a parent. Mixins are different: they're explicitly inline, share the host's URL namespace, and exist to compose with the host. Inheritance is the right model for mixins; isolation is the right model for children."
That divergence is the heart of the 10.0.0 mixin model. The rest of this page is the user-facing reference for what inherits, what doesn't, and how to opt out.
The host-to-mixin inheritance model
The canonical motivating use case is YAML serialization for OpenAPI documents. A REST app's
/items endpoint should not advertise application/yaml in its Content-Type negotiation, but
its /openapi.yaml endpoint must serve YAML. With mixin sub-context inheritance, the YAML
serializer can live on the mixin alone:
// Mixin: declares a YamlSerializer that applies only to mixin endpoints.
@Rest(
paths = {"/openapi", "/openapi.json", "/openapi.yaml"},
serializers = {YamlSerializer.class}
)
public class OpenApiResource { // (illustrative simplified version)
@RestGet(path="/openapi.yaml")
public OpenApi yaml(RestRequest req) { return req.getOpenApi(); }
// ... /openapi and /openapi.json handlers ...
}
// Host: a vanilla RestServlet, no YAML declared.
@Rest(mixins = OpenApiResource.class)
public class ApiResource extends RestServlet {
@RestGet("/items")
public List<Item> items() { ... }
}
After construction, the resolved serializer sets look like this:
| Context | Resolved serializers |
|---|---|
ApiResource RestContext | host defaults (JSON, XML, HTML, …) |
OpenApiResource mixin sub-ctx | host defaults (JSON, XML, HTML, …) + YamlSerializer |
Concrete request dispatch:
| Request | Resolves through | Result |
|---|---|---|
GET /items | host context | JSON/XML/HTML negotiation as normal |
GET /items Accept: application/yaml | host context | 406 Not Acceptable (host has no YAML) |
GET /openapi.yaml | mixin sub-context | 200, YAML body |
GET /openapi Accept: application/yaml | mixin sub-context | 200, YAML body |
The mixin's @Rest(serializers={YamlSerializer.class}) declaration appends to the inherited
host set, so the mixin sub-context sees the full host serializer chain plus YAML. The host's
chain is unaffected — /items doesn't grow a YAML serializer just because YAML appears on a
mixin.
The contribution lists that inherit
The Phase 2 inheritance walk extends RestContext.getRestAnnotationsForProperty(name) to prepend
the parent's @Rest chain onto the mixin's own. That single change cascades to every
contribution list because the *Builder memoizers and configuration accessors on RestContext
all read through getRestAnnotationsForProperty(...). The framework verifies inheritance for the
following thirteen contribution lists in juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mixin/
(one regression test class per list):
@Rest property | Inheritance behavior | noInherit token |
|---|---|---|
serializers | Host first, then mixin appended. Mixin endpoint sees union. | "serializers" |
parsers | Host first, then mixin appended. Mixin endpoint sees union. | "parsers" |
encoders | Host first, then mixin appended. Mixin endpoint sees union. | "encoders" |
partSerializer | Mixin overrides host if declared; else inherits host's resolved value. | "partSerializer" |
partParser | Mixin overrides host if declared; else inherits host's resolved value. | "partParser" |
converters | Host first, then mixin appended. Mixin endpoint runs both chains. | "converters" |
responseProcessors | Host first, then mixin appended. Mixin endpoint runs both chains. | "responseProcessors" |
restOpArgs | Host first, then mixin appended. Mixin can add custom arg resolvers. | "restOpArgs" |
guards | Host first, then mixin appended. Inherit-by-default for security. | "guards" |
messages | Mixin's bundle chained as child of host's via Messages.chain(...). | "messages" |
"Host first, then mixin appended" means the mixin endpoint's resolver chain is the concatenation of the host's and the mixin's, in that order. For list-shaped properties (serializers, parsers, encoders, converters, response processors, REST op args, guards), the chain produced is the union — useful for accumulating contributions. For replace-shaped properties (partSerializer, partParser), the mixin's value (when present) wins over the host's because the walk uses last-write-wins on the property's annotation chain.
Tokens in the noInherit column are the exact case-insensitive strings to put in
@Rest(noInherit={...}) on the mixin to cut off inheritance for that property — semantics
identical to the framework's existing @RestOp(noInherit={...}) and @Rest(noInherit={...})
machinery on the host's own class chain.
Hook dual-firing
Lifecycle hooks (@RestStartCall, @RestEndCall, @RestPreCall, @RestPostCall, and
@RestDestroy) participate in the mixin model with a strict ordering contract:
- Host-endpoint request. Only host hooks fire. The mixin's hook chains are not visited.
- Mixin-endpoint request. Host hooks fire first; then the mixin's hooks fire. Hooks declared on the host's class always run before hooks declared on the mixin's class, even for requests that land on mixin endpoints. The dispatcher walks the parent context's invoker list before the mixin's.
- Application shutdown.
@RestDestroyfires on every context — host first, then each mixin's destroy chain in mixin-discovery order.
The "host first" rule is the natural consequence of the parent-walk model: from a mixin
sub-context's point of view, the host context is its parent, and parent hooks run before the
local class's hooks (consistent with how the framework has always handled @RestStartCall on a
parent class vs. a subclass).
Putting it together, a GET /openapi.yaml request against the example above traces this
sequence:
ApiResource's@RestStartCallmethods (host's start-call invoker list).OpenApiResource's@RestStartCallmethods (mixin's start-call invoker list).- Host's
@RestPreCallmethods. - Mixin's
@RestPreCallmethods. - The matched
@RestGetoperation method onOpenApiResource. - Mixin's
@RestPostCallmethods. - Host's
@RestPostCallmethods. - Mixin's
@RestEndCallmethods. - Host's
@RestEndCallmethods.
(@RestPreCall is host-then-mixin to match the start-call ordering. @RestEndCall and
@RestPostCall reverse the order — mixin-then-host — so unwinds match initialization, like
nested try/finally blocks.)
The noInherit opt-out per property
Every contribution list inherits by default. @Rest(noInherit={...}) on the mixin's class is the
opt-out — the token set is the same as the property names in the table above, and the framework's
existing noInherit machinery handles them uniformly. Three worked examples:
noInherit={"serializers"} — the mixin owns its serializer chain, no host inheritance.
// Plain-text-only mixin: only the mixin's serializers, no host JSON/XML/HTML.
@Rest(
noInherit = {"serializers", "parsers"},
serializers = {PlainTextSerializer.class},
parsers = {PlainTextParser.class}
)
public class RawTextResource {
@RestPut(path="/text") public void put(@Content String body) { ... }
@RestGet(path="/text") public String get() { ... }
}
Mixin endpoints emit and accept text/plain exclusively. Host endpoints retain
whatever serializer chain they had.
noInherit={"guards"} — the mixin opts out of host guard protection.
// Example probe mixin: explicitly unguarded even if host has BearerTokenGuard.
@Rest(
paths = {"/healthz", "/readyz", "/livez"},
noInherit = {"guards"}
)
public class UnguardedProbeMixin { ... }
Without noInherit={"guards"}, the host's BearerTokenGuard would also protect /healthz — the
inherit-by-default behavior is the security-conservative choice (see Guard inheritance security
note below).
noInherit={"messages"} — the mixin owns its message bundle, no host fallthrough.
@Rest(
messages = "MixinMessages",
noInherit = {"messages"}
)
public class MixinWithIsolatedMessages { ... }
Mixin's $L{key} lookups resolve from MixinMessages.properties only — they do not fall through
to the host's bundle. Without noInherit, the mixin's bundle is chained as a child of the host's
via Messages.chain(...) (see Messages.chain(...) as the public seam
below) so any key absent from the mixin's bundle resolves through the host's.
Flat inheritance for transitive mixins
A mixin can declare @Rest(mixins=...) of its own. The framework discovers the transitive
closure and registers every reachable mixin against the host. All discovered mixins
parent-link directly to the host — never to a transitive predecessor. If A mixes in B,
both A and B end up with parentContext = host. They do not form an A → B inheritance
chain.
@Rest(serializers=YamlSerializer.class)
public class B {
@RestGet(path="/b") public String b() { return "b"; }
}
@Rest(mixins=B.class, serializers=ProtobufSerializer.class)
public class A {
@RestGet(path="/a") public String a() { return "a"; }
}
@Rest(mixins=A.class, serializers=XmlSerializer.class)
public class HostResource extends BasicRestServlet { ... }
Resolved serializer sets:
| Context | Parent | Resolved serializer chain |
|---|---|---|
HostResource (host) | (none) | XmlSerializer |
A mixin sub-ctx | host | XmlSerializer, ProtobufSerializer |
B mixin sub-ctx | host | XmlSerializer, YamlSerializer |
Notice the flat structure: B's sub-context inherits from the host (XmlSerializer), not from
A (no ProtobufSerializer). The rationale:
"Predictable: a mixin always inherits from 'the host,' period. Order-independent: the discovery order of A and B doesn't matter for B's resolution. Composable: a mixin can be added or removed without rearranging inheritance chains."
If a use case for cross-mixin inheritance ever emerges, it'll be modeled as an explicit mechanism (some new annotation member) rather than by reshaping the discovery walk. The flat-inheritance rule is load-bearing for the v1 model.
Per-mixin overrides for host settings
Because each mixin owns its own RestContext, it can declare any host setting — including
typically host-wide concerns like guards and partSerializer/partParser — and the
override applies only to that mixin's endpoints. The host's resolution chain is unaffected.
// Host: no guard chain, default part serializer.
@Rest(
mixins = AdminResource.class
)
public class ApiResource extends BasicRestServlet {
@RestGet("/items") public List<Item> items() { ... }
}
// Mixin: BearerToken-only, its own part serializer.
@Rest(
paths = {"/admin/threads", "/admin/heap"},
partSerializer = AdminPartSerializer.class,
guards = AdminBearerGuard.class
)
public class AdminResource {
@RestGet(path="/admin/threads") public String threads() { ... }
@RestGet(path="/admin/heap") public String heap() { ... }
}
Concrete observability:
| Request | partSerializer | Guard chain |
|---|---|---|
GET /items | host's default | host's (none in this example) |
GET /admin/threads | AdminPartSerializer | host's (none) + AdminBearerGuard (inherited+appended) |
The admin mixin's override is scoped to admin endpoints — it doesn't bleed back into the host's
/items part serialization or guard chain. If the mixin wanted only its own part serializer (no
inheritance), it would add noInherit={"partSerializer"} (though for partSerializer and the other
replace-shaped properties, "declare an explicit value on the mixin" already wins over the parent
walk — noInherit is mostly meaningful for the list-shaped properties where inheritance produces
a union).
Host-side overrides with @Mixin (10.0.0)
The override above lives on the mixin class. But the composition decision — "for this import,
also apply AdminGuard" — often belongs to the host, where the mixin is being assembled. Editing
the mixin class to add a guard couples it to one host; subclassing it just to change a setting is
boilerplate. 10.0.0 adds @Rest(mixinDefs=@Mixin(...)) — a host-side rich mixin declaration that
names the mixin and overrides selected @Rest-level settings for that mixin's endpoints, in one
place, without touching the mixin class:
@Rest(
mixinDefs = @Mixin(type = AdminResource.class, guards = AdminGuard.class)
)
public class ApiResource extends BasicRestServlet {
@RestGet("/items") public List<Item> items() { ... }
}
AdminGuard now protects AdminResource's endpoints as declared by the host — AdminResource
itself declares nothing.
mixinDefs is additive to and coexists with mixins: bare-class entries in mixins= and rich
@Mixin entries in mixinDefs= are discovered together. A @Mixin(type=X.class) with no overrides
is exactly equivalent to a bare mixins=X.class entry; if both name the same class, the rich entry
wins (the bare entry is upgraded in place).
Override slots
@Mixin exposes override slots mirroring the composition-shaped @Rest attributes:
guards, roleGuard/rolesDeclared, converters, encoders, serializers/parsers,
responseProcessors, restOpArgs, callLogger, partSerializer/partParser, debug,
messages, defaultRequestHeaders/defaultResponseHeaders/defaultRequestAttributes,
produces/consumes/defaultAccept/defaultContentType/defaultCharset, and maxInput.
Folding a mixin's contributions into the host
The override slots above apply the host's values to the mixin's endpoints. The reverse
direction — appending the mixin's own list-shaped @Rest contributions into the host's own
chain, so the host's own @RestOp methods pick them up too — is available via two opt-in
directives. Both are strictly opt-in and non-silent: a mixin that declares neither keeps today's
default scoping exactly (host endpoints see only the host's chain).
| Directive | Declared on | Folds | Use when |
|---|---|---|---|
@Mixin(mergeIntoHost=true) | the host (via mixinDefs) | all of the mixin's list-shaped @Rest attributes (serializers, parsers, encoders, converters, guards, responseProcessors, restOpArgs, default headers/attributes, produces, consumes) | the host wants to adopt a mixin's full list-shaped contribution surface into its own endpoints |
@Rest(mergeResponseProcessorsIntoHost=true) | the mixin class itself | only the mixin's responseProcessors | a mixin (typically a view-renderer mixin) wants "declare the mixin → it just works" for the host's own returns, under a plain mixins= reference, without folding anything but its renderer |
// Host-side, broad: fold ALL of AdminResource's list-shaped attributes into the host chain.
@Rest(mixinDefs = @Mixin(type = AdminResource.class, mergeIntoHost = true))
public class ApiResource extends BasicRestServlet { ... }
// Mixin-declared, narrow: a view mixin folds ONLY its renderer into any plain-mixins= host.
@Rest(responseProcessors = FreemarkerViewRenderer.class, mergeResponseProcessorsIntoHost = true)
public class FreemarkerMixin { ... }
mergeResponseProcessorsIntoHost is what makes a bare @Rest(mixins=FreemarkerMixin.class) render
the host's own FreemarkerView returns (see FreeMarker View Support
and the sibling JSP / Thymeleaf / Mustache bridges, which all declare it). Same-class de-duplication
applies to both directives, so a response processor the host already declares is never added twice.
Precedence and noInherit
A host @Mixin override resolves as if it were declared on the mixin class itself, but at the
most-derived position — so it layers on top of the inherited host chain (list-shaped props append
after it; replace-shaped props take the override as the winning value), and it also wins over the
mixin class's own same-property declaration.
@Mixin carries its own noInherit attribute, which cuts the host→mixin inheritance walk for
the named properties — unioned with the mixin class's own @Rest(noInherit=...). So the host
override is authoritative but overridable by the @Mixin's own noInherit rules:
// AdminGuard applies; the host's own guard chain is NOT inherited onto AdminResource's endpoints.
@Rest(
guards = HostGuard.class,
mixinDefs = @Mixin(type = AdminResource.class, guards = AdminGuard.class, noInherit = "guards")
)
public class ApiResource extends BasicRestServlet { ... }
Host-chosen re-mount (path/paths)
@Mixin can also re-mount the mixin's endpoints under host-chosen prefix(es) via path/paths,
reusing the same pathToken normalization as the SVL-configurable bundled mixins (leading/trailing
slashes and a trailing /* collapse to one clean prefix):
// AdminResource's /threads and /heap endpoints mount at /admin/threads and /admin/heap.
@Rest(mixinDefs = @Mixin(type = AdminResource.class, path = "/admin"))
public class ApiResource extends BasicRestServlet { ... }
Transitive mixins
A host override applies only to the directly-named mixin. If AdminResource transitively pulls
in another mixin, that transitive mixin is overridden only if the host names it separately in its own
@Mixin.
Messages.chain(...) as the public seam
@Rest(messages=...) doesn't ride the standard getRestAnnotationsForProperty(...) walk —
Messages has its own ResourceBundle-based parent-chain mechanism that pre-dates this redesign.
To keep mixin-Messages inheritance working without a special-case in Messages.Builder, 10.0.0
introduces a new static factory on Messages:
public static Messages chain(Messages child, Messages parent)
Messages.chain(child, parent) returns a new parent-chained bundle whose lookups read child
first and fall through to parent for any key not found in child's chain. Neither input is
mutated: the factory deep-copies child's existing parent2 chain, splicing parent in at the
deepest position. The new chain shares the inner ResourceBundle instances with the inputs
(read-only, so safe).
The mixin's RestContext.messages memoizer uses it like this:
var local = build the mixin's own Messages bundle from @Rest(messages=...);
return Messages.chain(local, hostContext.getMessages());
That single line makes the mixin's bundle inherit from the host's. @Rest(noInherit={"messages"})
on the mixin skips the wrap so the mixin sees its bundle in isolation.
The factory is a general-purpose composition primitive — user code that needs to thread one
pre-built Messages bundle through another (a CMS-style overlay of per-tenant strings on top of
app-wide strings, for example) can call Messages.chain(...) directly without going through the
mixin machinery.
Mixin-vs-child divergence
Mixins inherit from the host. Children do not. This is the central design decision of the 10.0.0 mixin model, and it's the one to internalize before reaching for either primitive. The rationale:
"Child resources tend to be completely independent from parents — they're externally-mounted at their own URL namespace, often have their own deployment lifecycle (factored into separate jars, etc.), and conflating their resolution with the host's would surprise authors who treat children as standalone REST endpoints that happen to be discovered through a parent. Mixins are different: they're explicitly inline, share the host's URL namespace, and exist to compose with the host. Inheritance is the right model for mixins; isolation is the right model for children."
A quick checklist:
Use a mixin (@Rest(mixins=...)) when… | Use a child (@Rest(children=...)) when… |
|---|---|
The addon lives inline at the host's URL namespace (/healthz next to /items). | The addon owns its own URL subtree (/admin/* under /). |
| The addon should inherit the host's serializer / guard / hook chain. | The addon should be isolated from the host's resolution chain. |
The addon is a lightweight composer (small set of @RestOp methods). | The addon is a heavyweight independent resource (own DB, own lifecycle). |
| You want per-addon overrides scoped to the addon's endpoints. | You want per-resource isolation between addon and host. |
Most "I want to add probes / version endpoint / favicon / OpenAPI docs to my API" use cases are mixins. Most "I want to mount an unrelated REST surface under the same servlet" use cases are children. The divergence is not deferred — it's the intentional v1 model.
Guard inheritance security note
Guards inherit by default. This is a deliberate "fail strict" choice:
"Surprises around security defaults should err on 'too strict' (mixin endpoint accidentally protected) not 'too loose' (mixin endpoint accidentally exposed). A
BearerTokenGuardon the host automatically protects mixin endpoints unless the mixin explicitly opts out."
Concretely: if the host declares @Rest(guards=BearerTokenGuard.class), every mixin endpoint on
that host is also protected by BearerTokenGuard — even if the mixin's class declares no guards
itself. To expose a mixin endpoint without the host's guard chain, either declare
@Rest(noInherit={"guards"}) on the mixin's own class, or — for a shipped mixin you can't edit —
apply the same opt-out from the host side via @Mixin(..., noInherit="guards") (see
Host-side overrides with @Mixin above).
Health probes are the natural real-world case for this opt-out: probes (/healthz, /readyz,
/livez) need to be reachable by the load balancer / orchestrator even when the rest of the API
requires authentication. The shipped HealthMixin (org.apache.juneau.rest.server.health)
declares no guards and no noInherit of its own, so composing it into a guarded host inherits
that host's guard chain by default, same as any other mixin. To keep the probes reachable
without authentication, opt out from the host side:
@Rest(
guards = BearerTokenGuard.class,
mixinDefs = @Mixin(type = HealthMixin.class, noInherit = "guards")
)
public class RootResources extends BasicRestServlet { ... }
Alternatively, mount probes via the standalone HealthServlet flavor instead of the mixin —
it's a separate servlet with its own RestContext, so the host's guard chain never applies to
it at all. See Health / Readiness / Liveness Probes for both
options.
The asymmetry — host adds a guard, mixin endpoints become protected unless the mixin opts out —
is intentional. Accidentally protected is recoverable (the user sees a 401/403, the mixin
author adds noInherit); accidentally exposed is not (the endpoint is reachable to attackers
before anyone notices).
See also
- REST Server — Mixins and Multi-Mount Paths
— the broader
@Rest(mixins=...)/@Rest(paths=...)reference. This topic page is the sub-context-and-inheritance follow-on. - Child Resources — the alternative composition primitive when isolation (not inheritance) is what you want.
- REST Server — Children vs Mixins — the full same-vs-different matrix for choosing between the two.
- Health / Readiness / Liveness Probes — the mixin, standalone,
and child flavors of the built-in probe resource, and the host-side
noInherit={"guards"}opt-out pattern for the mixin flavor. - REST Server — Logging and Debugging — the JUL-logger-level model that controls request/response debug capture.
- REST Server — SVL Variables — context for the
$L{key}variable referenced from the Messages section.