This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new 52879c3d3a Refresh Marshalling overview and docs cleanup
52879c3d3a is described below
commit 52879c3d3a92fd08d57bffbdfc07a346990b6eb3
Author: James Bognar <[email protected]>
AuthorDate: Mon Jun 29 08:01:40 2026 -0400
Refresh Marshalling overview and docs cleanup
- MarshallingOverview: language pills now link to topic pages; remove
obsolete
JSON5/UON/YAML sections; expand SVL Variables with 10.0 features.
- Update obsolete code examples (binaryFormat/calendarFormat,
JettyMicroservice,
MarshalledMap.get); remove "Since x.y.z" prose references.
- Fix sidebar topic ordering/numbering; add RestServerAuthenticator topic.
Co-authored-by: Cursor <[email protected]>
---
.../02.06.CodeFormattingStylesheet.md | 2 +-
pages/release-notes/10.0.0.md | 34 +++
pages/topics/01.02.Marshalling.md | 302 +++++++++------------
pages/topics/02.04.05.BeanPropAnnotation.md | 4 +-
pages/topics/02.10.SerializerSetsParserSets.md | 7 +-
pages/topics/02.16.ParsingIntoGenericModels.md | 4 +-
pages/topics/02.21.PojoCategories.md | 8 +-
pages/topics/02.25.01.JsonBasics.md | 2 +-
pages/topics/02.26.JsonSchemaDetails.md | 4 +-
pages/topics/02.27.01.XmlBasics.md | 2 +-
pages/topics/02.27.08.XmlNamespaces.md | 2 +-
pages/topics/02.30.01.UonBasics.md | 2 +-
pages/topics/02.31.01.UrlEncodingBasics.md | 2 +-
pages/topics/02.35.01.YamlBasics.md | 2 +-
pages/topics/04.04.JuneauBeanJsonSchema.md | 6 +-
pages/topics/10.06.Marshalling.md | 14 +-
pages/topics/10.07.HandlingFormPosts.md | 2 +-
pages/topics/10.08.RestServerComposition.md | 4 +-
pages/topics/10.13.Guards.md | 8 +-
pages/topics/10.38.RestServerAuthGuards.md | 2 +-
pages/topics/10.42.01.RestServerAuthenticator.md | 265 ++++++++++++++++++
pages/topics/10.42.AuthFilterFramework.md | 14 +-
.../12.01.JuneauRestServerSpringbootBasics.md | 2 +-
sidebars.ts | 138 +++++-----
24 files changed, 539 insertions(+), 293 deletions(-)
diff --git a/pages/developer-info/02.06.CodeFormattingStylesheet.md
b/pages/developer-info/02.06.CodeFormattingStylesheet.md
index dd95b12853..538ad87575 100644
--- a/pages/developer-info/02.06.CodeFormattingStylesheet.md
+++ b/pages/developer-info/02.06.CodeFormattingStylesheet.md
@@ -157,7 +157,7 @@ Special list item classes for displaying Java class
hierarchies:
* Example method with highlighted code.
*
* <p class='bjava'>
- * <jk>public</jk> <jk>void</jk> processData(<jk>String</jk>
<jv>input</jv>) {
+ * <jk>public</jk> <jk>void</jk> processData(<jx>String</jx>
<jv>input</jv>) {
* <jc>// Process the input</jc>
* System.<jsm>out</jsm>.println(<js>"Processing: "</js> +
<jv>input</jv>);
* }
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index f08d5facd9..c0982e6d91 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -318,6 +318,27 @@ Juneau 10.0 extends its request-boundary observability to
cover custom (non-requ
See the extended [Observability — Micrometer +
OpenTelemetry](/docs/topics/RestServerObservability) topic page.
+#### Resource-level authentication — `RestAuthenticator`
+
+Juneau 10.0 adds a `RestAuthenticator` SPI
(`org.apache.juneau.rest.server.auth`) — an ergonomic, **in-resource** way to
resolve a request's `Principal` + roles so `roleGuard`, `RoleBasedRestGuard`,
`@Auth Principal`, and `req.isUserInRole(...)` all work with no
container-managed security and no servlet-layer filter. It **complements** the
[AuthN Filter Framework](/docs/topics/AuthFilterFramework) and reuses the same
underlying auth implementations.
+
+- **Shared `Authenticator` contract** — a new `Authenticator` functional
interface (`Optional<AuthResult> authenticate(HttpServletRequest)`) is now
implemented by both `AuthFilter` and `AuthFilterChain`, so every existing
filter (SAML / OAuth / JWT / API-key / bearer) is reusable verbatim at the
resource layer. `RestAuthenticator.of(Authenticator)` adapts any of them.
+- **`AuthResult` merge modes** — `AuthResult` gains an immutable merge mode:
`of(...)` = `ADD` (union roles, principal required — unchanged), `ofRoles(...)`
= roles-only `ADD` (null principal — keeps the inherited identity),
`replacing(...)` = `REPLACE` (swap identity outright). The existing `of(...)`
factories and behavior are preserved (backward compatible).
+- **Registration & DI** — resolved per `RestContext` with precedence
registered bean › `@Bean` method › `@Rest(authenticator=...)` ›
`RestServlet.createAuthenticator(BeanStore)`. Both
`Authenticator`-into-`RestAuthenticator` and `RestAuthenticator`-into-resource
injection work through the bean store.
+- **Inheritance & folding** — unlike guards, authenticators inherit down the
child-resource tree: a single root authenticator covers all descendants, which
may **augment** (add roles), **replace** (swap identity), or opt out with
`@Rest(noInherit={"authenticator"})`.
+
+```java
+@Rest(children = { ChildResource.class })
+public class RootResource extends BasicRestServlet {
+ @Bean
+ public RestAuthenticator authenticator(AuthFilterChain chain) {
+ return RestAuthenticator.of(chain); // covers root + all descendants
+ }
+}
+```
+
+See the new [REST Authenticator — Resource-Level
Authentication](/docs/topics/RestServerAuthenticator) topic page.
+
### juneau-rest-client / juneau-rest-common
#### Next-generation remote-proxy declarative features
@@ -566,6 +587,19 @@ _TBD — to be filled in as development continues._
Note: the `RecordReader.read()` and `RecordWriter.write()` **cursor**
methods are **not** renamed; only the top-level factory methods on marshaller
instances changed. Update call sites by replacing `.read(` with `.to(` and
`.write(` with `.of(` on marshaller instances, and rename streaming factory
calls accordingly.
+- **`SerializerSet` / `ParserSet` lookup methods now return `Optional<...>`
(TODO-190).** Every media-type lookup method on `SerializerSet` and `ParserSet`
now returns a `java.util.Optional` instead of `null` on no-match, aligning
these classes with the `Optional`-returning idiom already used by their
consumers (`RestResponse.getSerializerMatch()`,
`RequestContent.getParserMatch()`). Changed signatures:
+ - `SerializerSet`: `getSerializer(MediaType)` / `getSerializer(String)` →
`Optional<Serializer>`; `getSerializerMatch(MediaType)` /
`getSerializerMatch(String)` → `Optional<SerializerMatch>`;
`getStreamSerializer(MediaType)` / `getStreamSerializer(String)` →
`Optional<OutputStreamSerializer>`; `getWriterSerializer(MediaType)` /
`getWriterSerializer(String)` → `Optional<WriterSerializer>`.
+ - `ParserSet`: `getParser(MediaType)` / `getParser(String)` →
`Optional<Parser>`; `getParserMatch(MediaType)` / `getParserMatch(String)` →
`Optional<ParserMatch>`.
+ - The `getSerializers()`, `getParsers()`, and `getSupportedMediaTypes()`
collection getters are unchanged (they were never `null`).
+ - Both `MediaType` overloads now guard `null` and return `Optional.empty()`
(previously `ParserSet.getParserMatch(MediaType)` threw an NPE on a `null`
argument).
+ - Migration: unwrap with `.map(...)` / `.orElseThrow(...)` /
`.ifPresent(...)`, or `.orElse(null)` to preserve a prior null contract. For
the HTTP-server direction, throwing the status-correct exception is idiomatic —
`NotAcceptable` (406) for a failed `Accept`/serializer lookup and
`UnsupportedMediaType` (415) for a failed `Content-Type`/parser lookup. The
next-gen (Beta) `RestClient.getSerializerForMediaType(String)`,
`getParserForMediaType(String)`, and `getMatchingParser(String)` l [...]
+
+- **Next-gen (Beta) `RestClient` no longer falls back to JSON implicitly
(behavioral change).** The next-generation
`org.apache.juneau.rest.client.RestClient` previously treated a
fully-unconfigured client as "JSON in / JSON out" and also used a lone
registered parser/serializer regardless of media type. Both implicit behaviors
are **removed**: content negotiation now resolves a parser/serializer **only**
via an exact media-type match or an explicitly-configured default, otherwise it
res [...]
+ - New opt-in builder knobs restore the old behavior deliberately:
`RestClient.Builder.defaultParser(Parser)` and
`RestClient.Builder.defaultSerializer(Serializer)` (e.g.
`.defaultParser(JsonParser.DEFAULT).defaultSerializer(JsonSerializer.DEFAULT)`).
+ - Resolution precedence is now: exact media-type match →
explicitly-configured default → none.
+ - A response whose `Content-Type` matches no registered parser (and with no
default parser configured) now fails with **`UnsupportedMediaType`** (HTTP 415)
instead of silently parsing as JSON — affecting `ResponseBody.as(...)` and
remote-proxy (`@Remote`) response deserialization. On the request/serializer
side, serializing a POJO body with no matching or default serializer now throws
a clear client-side `IllegalStateException` rather than defaulting to JSON.
+ - Migration: if you relied on the out-of-the-box JSON behavior, add
`.defaultParser(JsonParser.DEFAULT)` and/or
`.defaultSerializer(JsonSerializer.DEFAULT)` to your `RestClient.Builder`. The
classic `org.apache.juneau.rest.client.classic.RestClient` is unaffected.
+
_Other entries TBD — to be filled in before release. See also the major
version bump note above._
### Deprecations
diff --git a/pages/topics/01.02.Marshalling.md
b/pages/topics/01.02.Marshalling.md
index 1183d56c65..1e9f3db037 100644
--- a/pages/topics/01.02.Marshalling.md
+++ b/pages/topics/01.02.Marshalling.md
@@ -11,43 +11,43 @@ intermediate Document Object Models making them extremely
efficient.
Supported languages include:
<div>
- <span className="badge badge--secondary margin--xs">JSON</span>
- <span className="badge badge--secondary margin--xs">JSON5</span>
- <span className="badge badge--secondary margin--xs">JSONL</span>
- <span className="badge badge--secondary margin--xs">JCS</span>
- <span className="badge badge--secondary margin--xs">XML</span>
+ <a href="/docs/topics/JsonBasics"><span className="badge badge--secondary
margin--xs">JSON</span></a>
+ <a href="/docs/topics/Json5"><span className="badge badge--secondary
margin--xs">JSON5</span></a>
+ <a href="/docs/topics/JsonlBasics"><span className="badge badge--secondary
margin--xs">JSONL</span></a>
+ <a href="/docs/topics/JcsBasics"><span className="badge badge--secondary
margin--xs">JCS</span></a>
+ <a href="/docs/topics/XmlBasics"><span className="badge badge--secondary
margin--xs">XML</span></a>
<span className="badge badge--secondary margin--xs">SOAP/XML</span>
- <span className="badge badge--secondary margin--xs">HTML</span>
- <span className="badge badge--secondary margin--xs">YAML</span>
- <span className="badge badge--secondary margin--xs">TOML</span>
- <span className="badge badge--secondary margin--xs">HOCON</span>
- <span className="badge badge--secondary margin--xs">HJSON</span>
- <span className="badge badge--secondary margin--xs">INI</span>
- <span className="badge badge--secondary margin--xs">CBOR</span>
- <span className="badge badge--secondary margin--xs">BSON</span>
- <span className="badge badge--secondary margin--xs">MessagePack</span>
- <span className="badge badge--secondary margin--xs">Protobuf</span>
- <span className="badge badge--secondary margin--xs">Prototext</span>
- <span className="badge badge--secondary margin--xs">UON</span>
- <span className="badge badge--secondary margin--xs">URL-Encoding</span>
- <span className="badge badge--secondary margin--xs">OpenAPI</span>
- <span className="badge badge--secondary margin--xs">CSV</span>
- <span className="badge badge--secondary margin--xs">Parquet</span>
- <span className="badge badge--secondary margin--xs">Markdown</span>
- <span className="badge badge--secondary margin--xs">SSE</span>
+ <a href="/docs/topics/HtmlBasics"><span className="badge badge--secondary
margin--xs">HTML</span></a>
+ <a href="/docs/topics/YamlBasics"><span className="badge badge--secondary
margin--xs">YAML</span></a>
+ <a href="/docs/topics/TomlBasics"><span className="badge badge--secondary
margin--xs">TOML</span></a>
+ <a href="/docs/topics/HoconBasics"><span className="badge badge--secondary
margin--xs">HOCON</span></a>
+ <a href="/docs/topics/HjsonBasics"><span className="badge badge--secondary
margin--xs">HJSON</span></a>
+ <a href="/docs/topics/IniBasics"><span className="badge badge--secondary
margin--xs">INI</span></a>
+ <a href="/docs/topics/CborBasics"><span className="badge badge--secondary
margin--xs">CBOR</span></a>
+ <a href="/docs/topics/BsonBasics"><span className="badge badge--secondary
margin--xs">BSON</span></a>
+ <a href="/docs/topics/MessagePackBasics"><span className="badge
badge--secondary margin--xs">MessagePack</span></a>
+ <a href="/docs/topics/ProtobufBinaryBasics"><span className="badge
badge--secondary margin--xs">Protobuf</span></a>
+ <a href="/docs/topics/ProtobufBasics"><span className="badge
badge--secondary margin--xs">Prototext</span></a>
+ <a href="/docs/topics/UonBasics"><span className="badge badge--secondary
margin--xs">UON</span></a>
+ <a href="/docs/topics/UrlEncodingBasics"><span className="badge
badge--secondary margin--xs">URL-Encoding</span></a>
+ <a href="/docs/topics/OpenApiBasics"><span className="badge badge--secondary
margin--xs">OpenAPI</span></a>
+ <a href="/docs/topics/CsvBasics"><span className="badge badge--secondary
margin--xs">CSV</span></a>
+ <a href="/docs/topics/ParquetBasics"><span className="badge badge--secondary
margin--xs">Parquet</span></a>
+ <a href="/docs/topics/MarkdownBasics"><span className="badge
badge--secondary margin--xs">Markdown</span></a>
+ <a href="/docs/topics/SseBasics"><span className="badge badge--secondary
margin--xs">SSE</span></a>
<span className="badge badge--secondary margin--xs">PlainText</span>
- <span className="badge badge--secondary margin--xs">RDF/XML</span>
- <span className="badge badge--secondary margin--xs">RDF/XML-ABBREV</span>
- <span className="badge badge--secondary margin--xs">N-Triple</span>
- <span className="badge badge--secondary margin--xs">N-Quads</span>
- <span className="badge badge--secondary margin--xs">N3</span>
- <span className="badge badge--secondary margin--xs">Turtle</span>
- <span className="badge badge--secondary margin--xs">TriG</span>
- <span className="badge badge--secondary margin--xs">TriX</span>
- <span className="badge badge--secondary margin--xs">JSON-LD</span>
- <span className="badge badge--secondary margin--xs">RDF/JSON</span>
- <span className="badge badge--secondary margin--xs">RDF/Proto</span>
- <span className="badge badge--secondary margin--xs">RDF/Thrift</span>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">RDF/XML</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">RDF/XML-ABBREV</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">N-Triple</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">N-Quads</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">N3</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">Turtle</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">TriG</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">TriX</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">JSON-LD</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">RDF/JSON</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">RDF/Proto</span></a>
+ <a href="/docs/topics/RdfBasics"><span className="badge badge--secondary
margin--xs">RDF/Thrift</span></a>
</div>
<br/>
@@ -166,10 +166,8 @@ The <a
href="/site/apidocs/org/apache/juneau/swaps/package-summary.html" target=
// Create a serializer from scratch programmatically using a builder.
JsonSerializer serializer = JsonSerializer
.create()
- .swaps(
- ByteArrayBase64Swap.class, // byte[] swapped with
base-64 encoded strings
- TemporalCalendarSwap.Rfc1123DateTime.class // Override default ISO
8601 format for Calendars
- )
+ .binaryFormat(BinaryFormat.BASE64) // byte[] serialized
as base-64 encoded strings
+ .calendarFormat(CalendarFormat.RFC_1123_DATE_TIME) // Override default
ISO 8601 format for Calendars
.build();
```
@@ -249,104 +247,6 @@ public class MyAnnotatedClass {...}
:::
-#### JSON 5 Marshalling
-
-The <a
href="/site/apidocs/org/apache/juneau/marshall/json/Json5Serializer.html"
target="_blank">Json5Serializer</a> class can be used to serialized POJOs
-into JSON 5 notation.
-JSON 5 is similar to JSON except for the following:
-
-- JSON attributes are only quoted when necessary.
-- Uses single-quotes for quoting.
-
-:::tip Example
-```java
-// Some free-form JSON.
-Map map = JsonMap.of(
- "foo", "x1",
- "_bar", "x2",
- " baz ", "x3",
- "123", "x4",
- "return", "x5",
- "", "x6"
-);
-```
-
-```json
-// Serialized to standard JSON
-{
- "foo": "x1",
- "_bar": "x2",
- " baz ": "x3",
- "123": "x4",
- "return": "x5",
- "": "x6"
-}
-```
-
-```js
-{
- foo: 'x1',
- _bar: 'x2',
- ' baz ': 'x3', // Quoted due to embedded spaces.
- '123': 'x4', // Quoted to prevent confusion with number.
- 'return': 'x5', // Quoted because it's a keyword.
- '': 'x6' // Quoted because it's an empty string.
-}
-```
-:::
-
-JSON 5 is still valid Javascript.
-The advantage to JSON5 is you can represent it in a Java String in minimal
form with minimal escaping.
-This is particularly useful in cases such as unit testing where you can easily
validate POJOs by simplifying them to
-JSON 5 and do a simple string comparison.
-
-```java
-WriterSerializer serializer = Json5Serializer.DEFAULT;
-assertString(serializer.toString(myPojo)).is("{foo:'bar',baz:123}");
-```
-
-:::info See Also
-
-- [JSON 5](/docs/topics/Json5) for more information.
-
-:::
-
-#### UON Marshalling
-
-The Marshalling API also supports UON (URL-Encoded Object Notation).
-It allows JSON-like data structures (OBJECT, ARRAY, NUMBER, BOOLEAN, STRING,
NULL) in HTTP constructs (query parameters,
-form parameters, headers, URL parts) without violating RFC2396.
-This allows POJOs to be converted directly into these HTTP constructs which is
not possible in other languages such as
-JSON.
-
-```javascript
-(
- id=1,
- name='John+Smith',
- uri=http://sample/addressBook/person/1,
- addressBookUri=http://sample/addressBook,
- birthDate=1946-08-12T00:00:00Z,
- addresses=@(
- (
- uri=http://sample/addressBook/address/1,
- personUri=http://sample/addressBook/person/1,
- id=1,
- street='100+Main+Street',
- city=Anywhereville,
- state=NY,
- zip=12345,
- isCurrent=true
- )
- )
-)
-```
-
-:::info See Also
-
-- [UON Details](/docs/topics/UonBasics) for more information.
-
-:::
-
#### OpenAPI Marshalling
The Marshalling API also supports schema-based OpenAPI serialization.
@@ -389,7 +289,7 @@ public void doGet(
#### Bean Property Schema Validation
-Since 9.5.0, parsers and serializers can also enforce JSON-Schema constraints
declared via
+Parsers and serializers can also enforce JSON-Schema constraints declared via
<a href="/site/apidocs/org/apache/juneau/commons/annotation/Schema.html"
target="_blank">`@Schema`</a>
on bean properties, independent of HTTP parts. Validation is opt-in via
`MarshallingContext.Builder.validateSchema()` and is powered by
@@ -418,37 +318,6 @@ the flag becomes a silent no-op.
:::
-#### YAML Marshalling
-
-The Marshalling API also supports YAML serialization and parsing, implemented
natively without any external
-library dependencies. The YAML serializer produces clean, human-readable
block-style output using
-indentation-based structure.
-
-```yaml
-name: John Smith
-birthDate: '1946-08-12T00:00:00Z'
-addresses:
- - street: 100 Main Street
- city: Anywhereville
- state: NY
- zip: 12345
- isCurrent: true
-```
-
-```java
-// Serialize to YAML
-String yaml = Yaml.of(myPojo);
-
-// Parse from YAML
-Person person = Yaml.to(yaml, Person.class);
-```
-
-:::info See Also
-
-- [YAML Basics](/docs/topics/YamlBasics) for more information.
-
-:::
-
#### JsonMap/JsonList
The <a href="/site/apidocs/org/apache/juneau/collections/JsonMap.html"
target="_blank">JsonMap</a> and <a
href="/site/apidocs/org/apache/juneau/collections/JsonList.html"
target="_blank">JsonList</a> collections classes allow you to programmatically
build generic JSON data structures.
@@ -492,9 +361,11 @@ SerializerSet serializerSet = SerializerSet
.build();
// Find the appropriate serializer by Accept type and serialize our POJO to
the specified writer.
-// Fully RFC2616 compliant.
+// Fully RFC2616 compliant. The lookup returns an Optional - a missing match
on the
+// Accept header maps to HTTP 406 (Not Acceptable).
serializerSet
.getSerializer("text/invalid, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0")
+ .orElseThrow(NotAcceptable::new) // HTTP 406 - no serializer matched the
Accept header
.serialize(person, myWriter);
// Construct a new parser group with configuration parameters that get applied
to all parsers.
@@ -503,8 +374,11 @@ ParserSet parserSet = ParserSet
.add(JsonParser.class, UrlEncodingParser.class);
.build();
+// The lookup returns an Optional - a missing match on the Content-Type maps to
+// HTTP 415 (Unsupported Media Type).
Person person = parserSet
.getParser("text/json")
+ .orElseThrow(UnsupportedMediaType::new) // HTTP 415 - no parser matched
the Content-Type
.parse(myReader, Person.class);
```
@@ -517,24 +391,90 @@ Person person = parserSet
#### SVL Variables
The <a href="/site/apidocs/org/apache/juneau/commons/svl/package-summary.html"
target="_blank">org.apache.juneau.commons.svl</a> package defines an API for a
language called "Simple Variable
-Language".
-In a nutshell, Simple Variable Language (or SVL) is text that contains
variables of the form `$varName{varKey}`.
+Language" (SVL) — text that contains variables of the form `$varName{varKey}`.
+Variables can be recursively nested and can return values that themselves
contain more variables.
+
+`VarResolver.DEFAULT` ships the following built-in source variables:
+
+| Variable | Description |
+|---|---|
+| `$S{key[,default]}` | Java system property (`System.getProperty`). |
+| `$E{key[,default]}` | OS environment variable (`System.getenv`). |
+| `$EF{key[,default]}` | Key from a `.env`-style env file on the filesystem. |
+| `$DE{key[,default]}` | Key from a `.env` dotenv file (same format,
alternative naming). |
+| `$A{key[,default]}` | Command-line argument passed to the JVM. |
+| `$MF{key[,default]}` | Entry from the classpath `META-INF/MANIFEST.MF`. |
+| `$P{key[,default]}` | Unified `Settings` source stack — consults system
properties, env vars, classpath `juneau.cfg`, per-microservice Config, Spring
`Environment`, and more in priority order. |
+| `${key:default}` | Shorthand for `$P{key,default}`. The Spring-style `:`
default separator is rewritten to `,` before delegation. |
+
+:::tip Example
+```java
+// Resolve a system-property variable.
+String home = VarResolver.DEFAULT.resolve("Java home: $S{java.home}");
- - Variables can be recursively nested within the varKey (e.g.
`$FOO{$BAR{xxx},$BAZ{xxx}}`).
- - Variables can also return values that themselves contain more variables.
+// Nested fallback chain: env var → system property → literal default.
+String val = VarResolver.DEFAULT.resolve("$E{MY_VAR,$S{my.var,not found}}");
+// Spring-style ${...} shorthand against the unified Settings stack.
+String title = VarResolver.DEFAULT.resolve("${myapp.title:Untitled}");
+```
+:::
+
+#### `#{...}` Function-call Syntax
+
+Alongside `$Var{...}` style variables, SVL supports a `#{name(args...)}`
function-call syntax backed by a catalog of ~68 built-in functions across 10
categories:
+
+| Category | Example functions |
+|---|---|
+| String | `upper`, `lower`, `trim`, `substring`, `split`, `join`, `format`,
`pathToken` |
+| Type conversion | `toInt`, `toLong`, `toDouble`, `toBoolean`, `toString` |
+| Arithmetic | `add`, `sub`, `mul`, `div`, `mod`, `min`, `max`, `abs`, `round`
|
+| Boolean | `not`, `and`, `or`, `eq`, `ne` |
+| Conditional | `if`, `switch`, `coalesce`, `notEmpty`, `case` |
+| Regex | `matches`, `replace`, `extract` |
+| Encoding | `urlEncode`, `urlDecode`, `base64Encode`, `base64Decode`,
`htmlEscape` |
+| Date/time | `now`, `today`, `formatDate`, `parseDate` |
+| Random/UUID | `uuid`, `randomInt`, `randomString` |
+| JSON navigation | `jsonPath`, `get`, `keys`, `values`, `size` |
+
+This syntax replaces 11 dedicated transformation `Var` classes (`$UC{}`,
`$IF{}`, `$SW{}`, etc.) that were removed in 10.0 in favor of the unified
function library.
+
+:::tip Example
```java
-// Use the default variable resolver to resolve a string that
-// contains $S (system property) variables
-String myProperty = VarResolver.DEFAULT.resolve("The Java home directory is
$S{java.home}");
+VarResolver.DEFAULT.resolve("#{upper(hello world)}"); //
→ "HELLO WORLD"
+VarResolver.DEFAULT.resolve("#{if(${prod:false}, prod-key, dev-key)}"); // →
"dev-key"
+VarResolver.DEFAULT.resolve("#{switch(${tier:bronze}, gold:1.0, *:0)}"); // →
"0"
+VarResolver.DEFAULT.resolve("#{uuid()}"); //
→ a random UUID
```
+:::
+
+User code can add custom functions via
`VarResolver.create().functions(MyFunction.class).build()`
+or via `ServiceLoader` discovery in
`META-INF/services/org.apache.juneau.commons.svl.VarFunction`.
+
+#### `VarTemplate` Compiled-Template API
+
+`VarResolver.compile(input)` tokenizes a template once and returns a reusable
+<a href="/site/apidocs/org/apache/juneau/commons/svl/VarTemplate.html"
target="_blank">VarTemplate</a>.
+This skips re-tokenization on every resolve call — useful at framework
injection sites or anywhere
+the same template is resolved repeatedly.
+
+```java
+// Compile once (e.g. at startup or in framework metadata).
+VarTemplate tpl = VarResolver.DEFAULT.compile("hello ${user.name:guest}");
+
+// Resolve many times without re-parsing.
+String s = tpl.resolve(VarResolver.DEFAULT.createSession());
+```
+
+`VarResolver.resolveSupplier(input)` wraps this into a thread-safe
`Supplier<String>` that opens a
+fresh session on every `.get()`, which pairs naturally with `@Value
Supplier<String>` field injection
+for live-reload patterns.
-The SVL variables are used widely throughout various annotations defined in
Juneau allowing many features to be
-configured via external sources such as configuration files or environment
variables/system properties.
-The SVL APIs are extensible allowing for the addition of new types of
variables.
+SVL variables are used widely throughout Juneau annotations, allowing many
features to be configured
+from external sources such as configuration files, environment variables, or
system properties.
:::info See Also
-- [Simple Variable Language](/docs/topics/SimpleVariableLanguageBasics) for
more information.
+- [Simple Variable Language](/docs/topics/SimpleVariableLanguageBasics) for
the full SVL reference including variable nesting, stable-value folding,
`Supplier<String>` field semantics, and the complete function catalog.
:::
\ No newline at end of file
diff --git a/pages/topics/02.04.05.BeanPropAnnotation.md
b/pages/topics/02.04.05.BeanPropAnnotation.md
index 1e45d4ade1..7dd718aa16 100644
--- a/pages/topics/02.04.05.BeanPropAnnotation.md
+++ b/pages/topics/02.04.05.BeanPropAnnotation.md
@@ -209,8 +209,8 @@ public class MyBean {
## Annotation Inheritance
-:::info Since 9.2.0
-Starting with Juneau 9.2.0, `@BeanProp` and `@Name` annotations are
automatically inherited when bean
+:::info
+`@BeanProp` and `@Name` annotations are automatically inherited when bean
property methods (getters, setters, extraKeys) are overridden in subclasses.
:::
diff --git a/pages/topics/02.10.SerializerSetsParserSets.md
b/pages/topics/02.10.SerializerSetsParserSets.md
index c910f635f7..ea9045c26a 100644
--- a/pages/topics/02.10.SerializerSetsParserSets.md
+++ b/pages/topics/02.10.SerializerSetsParserSets.md
@@ -16,10 +16,12 @@ SerializerSet serializers = SerializerSet.create()
.build();
// Find the appropriate serializer by Accept type and serialize our POJO to the
-// specified writer.
+// specified writer. The lookup returns an Optional, so unwrap it - a missing
+// match on the Accept header maps to HTTP 406 (Not Acceptable).
// Date/time types are serialized as ISO 8601 strings automatically.
serializers
.getSerializer("text/invalid, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0")
+ .orElseThrow(NotAcceptable::new) // HTTP 406 - no serializer matched the
Accept header
.serialize(myPerson, myWriter);
// Construct a new parser group with configuration parameters that get applied
to all parsers.
@@ -27,8 +29,11 @@ ParserSet parsers = ParserSet.create()
.add(JsonParser.class, UrlEncodingParser.class)
.build();
+// The lookup returns an Optional - a missing match on the Content-Type maps to
+// HTTP 415 (Unsupported Media Type).
Person myPerson = parsers
.getParser("text/json")
+ .orElseThrow(UnsupportedMediaType::new) // HTTP 415 - no parser matched
the Content-Type
.parse(myReader, Person.class);
```
diff --git a/pages/topics/02.16.ParsingIntoGenericModels.md
b/pages/topics/02.16.ParsingIntoGenericModels.md
index d08554aa12..574e521956 100644
--- a/pages/topics/02.16.ParsingIntoGenericModels.md
+++ b/pages/topics/02.16.ParsingIntoGenericModels.md
@@ -94,9 +94,7 @@ int id = map.getInt("id");
// Get a value convertable from a String.
URI uri = map.get(URI.class, "uri");
-// Get a value using a swap.
-TemporalCalendarSwap swap = new TemporalCalendarSwap.IsoInstant();
-Calendar birthDate = map.get(swap, "birthDate");
+Calendar birthDate = map.get("birthDate", Calendar.class);
// Get the addresses.
JsonList addresses = map.getList("addresses");
diff --git a/pages/topics/02.21.PojoCategories.md
b/pages/topics/02.21.PojoCategories.md
index 6e820c0f2d..36a2ab5801 100644
--- a/pages/topics/02.21.PojoCategories.md
+++ b/pages/topics/02.21.PojoCategories.md
@@ -58,7 +58,7 @@ The following chart shows POJOs categorized into groups and
whether they can be
`Map`, `Collection`, `Optional`, and array values are primitive/string,
collection, bean, or swapped types.<br />
`OptionalInt`, `OptionalLong`, and `OptionalDouble` are treated as
first-class optional primitive types
and serialize/parse identically to `Optional<Integer>`,
`Optional<Long>`, and `Optional<Double>`
- respectively <i>(since 10.0.0)</i>.
+ respectively.
</td>
<td style={{backgroundColor: 'lightgreen', textAlign:
'center'}}><b>yes</b></td>
<td style={{backgroundColor: 'lightgreen', textAlign:
'center'}}><b>yes</b></td>
@@ -89,7 +89,7 @@ The following chart shows POJOs categorized into groups and
whether they can be
</ul>
</td>
<td>
- <b>Streamable sequence types</b> <i>(since 9.5.0)</i><br />
+ <b>Streamable sequence types</b><br />
`Iterator`, `Iterable` (non-Collection), `Enumeration`, and
`java.util.stream.Stream`.<br />
These are serialized directly as arrays. Elements are written lazily for
text-based formats (JSON, XML, UON, URL Encoding, OpenAPI).
MsgPack, HTML, and CSV may materialize to a list internally due to
format constraints.<br />
@@ -111,7 +111,7 @@ The following chart shows POJOs categorized into groups and
whether they can be
</ul>
</td>
<td>
- <b>Date/time and duration types</b> <i>(since 9.5.0)</i><br />
+ <b>Date/time and duration types</b><br />
`Calendar`, `Date`, `Temporal` subtypes (`Instant`, `ZonedDateTime`,
`LocalDate`, etc.),
`XMLGregorianCalendar`, `java.time.Duration`, and `java.time.Period`.<br
/>
These are serialized and parsed natively as ISO 8601 strings without
requiring swaps.
@@ -164,7 +164,7 @@ The following chart shows POJOs categorized into groups and
whether they can be
<td style={{backgroundColor: 'salmon', textAlign: 'center'}}><b>no</b></td>
</tr>
<tr className="light bb">
- <td><b>Java Records</b> <i>(since 10.0.0)</i></td>
+ <td><b>Java Records</b></td>
<td>
Java record classes (`record Foo(String bar, int baz) {}`). Record
components are treated as bean
properties. The canonical all-args constructor is used for parsing, so
all record components are
diff --git a/pages/topics/02.25.01.JsonBasics.md
b/pages/topics/02.25.01.JsonBasics.md
index 427f97dfd5..c644baca30 100644
--- a/pages/topics/02.25.01.JsonBasics.md
+++ b/pages/topics/02.25.01.JsonBasics.md
@@ -16,7 +16,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/02.26.JsonSchemaDetails.md
b/pages/topics/02.26.JsonSchemaDetails.md
index bdc631b3cf..f74b8471d3 100644
--- a/pages/topics/02.26.JsonSchemaDetails.md
+++ b/pages/topics/02.26.JsonSchemaDetails.md
@@ -23,7 +23,7 @@ JsonSchema schema2 = JsonSchema.of(Person.class);
##### Schema Validation
-Since 9.5.0, parsers and serializers can also enforce `@Schema` constraints at
parse/serialize time by enabling
+Parsers and serializers can also enforce `@Schema` constraints at
parse/serialize time by enabling
`MarshallingContext.Builder.validateSchema()`. Validation is powered by
<a
href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.html"
target="_blank">JsonSchemaValidator</a>
in `juneau-bean-jsonschema`, which implements JSON Schema Draft 2020-12
semantics over the typed `JsonSchema` bean.
@@ -116,7 +116,7 @@ jsonSchema = serializer.serialize(Person.class);
}
```
-##### AI-friendly `summary` (since 9.5.0)
+##### AI-friendly `summary`
The `@Schema` annotation supports a `summary` field (alias `su`) for short,
single-line
descriptions intended for AI / LLM consumption, compact docs, and tooltips.
Unlike
diff --git a/pages/topics/02.27.01.XmlBasics.md
b/pages/topics/02.27.01.XmlBasics.md
index c4aefab697..20e3c2fbe1 100644
--- a/pages/topics/02.27.01.XmlBasics.md
+++ b/pages/topics/02.27.01.XmlBasics.md
@@ -21,7 +21,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/02.27.08.XmlNamespaces.md
b/pages/topics/02.27.08.XmlNamespaces.md
index 05a46da153..c67af21eb9 100644
--- a/pages/topics/02.27.08.XmlNamespaces.md
+++ b/pages/topics/02.27.08.XmlNamespaces.md
@@ -14,7 +14,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/02.30.01.UonBasics.md
b/pages/topics/02.30.01.UonBasics.md
index a5f8f8a04c..8ee49cce89 100644
--- a/pages/topics/02.30.01.UonBasics.md
+++ b/pages/topics/02.30.01.UonBasics.md
@@ -22,7 +22,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/02.31.01.UrlEncodingBasics.md
b/pages/topics/02.31.01.UrlEncodingBasics.md
index 2c100b5600..d53146a112 100644
--- a/pages/topics/02.31.01.UrlEncodingBasics.md
+++ b/pages/topics/02.31.01.UrlEncodingBasics.md
@@ -23,7 +23,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/02.35.01.YamlBasics.md
b/pages/topics/02.35.01.YamlBasics.md
index 8f51272c26..c67f6df00d 100644
--- a/pages/topics/02.35.01.YamlBasics.md
+++ b/pages/topics/02.35.01.YamlBasics.md
@@ -17,7 +17,7 @@ public class Person {
// Bean properties
public String name;
- @Swap(TemporalCalendarSwap.IsoInstant.class) public Calendar birthDate;
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_INSTANT) public Calendar
birthDate;
public List addresses;
// Getters/setters omitted
diff --git a/pages/topics/04.04.JuneauBeanJsonSchema.md
b/pages/topics/04.04.JuneauBeanJsonSchema.md
index d2da36f2c2..d32232a1b3 100644
--- a/pages/topics/04.04.JuneauBeanJsonSchema.md
+++ b/pages/topics/04.04.JuneauBeanJsonSchema.md
@@ -407,7 +407,7 @@ schema.addDefinition("myDef", new JsonSchema());
- <a href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaRef.html"
target="_blank">`JsonSchemaRef`</a> - Schema reference ($ref)
- <a
href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaArray.html"
target="_blank">`JsonSchemaArray`</a> - Array of schemas
- <a href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaMap.html"
target="_blank">`JsonSchemaMap`</a> - Map of schemas
-- <a
href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.html"
target="_blank">`JsonSchemaValidator`</a> - Validates values against a
`JsonSchema` bean using Draft 2020-12 semantics (since 9.5.0)
+- <a
href="/site/apidocs/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.html"
target="_blank">`JsonSchemaValidator`</a> - Validates values against a
`JsonSchema` bean using Draft 2020-12 semantics
## Schema Validation
@@ -427,7 +427,7 @@ v.validate("hello"); // OK
v.validate("Hi!"); // throws SchemaValidationException - pattern fails
```
-Integration with marshalling (since 9.5.0):
+Integration with marshalling:
```java
public class MyBean {
@@ -450,7 +450,7 @@ Supported keywords (v1): `type`, `enum`, `const`,
`minimum`/`maximum`/`exclusive
**Metadata:**
- `setTitle(String)` - Human-readable title
- `setDescription(String)` - Detailed description
-- `setSummary(String)` - Short, single-line summary suitable for AI / LLM
consumption (since 9.5.0)
+- `setSummary(String)` - Short, single-line summary suitable for AI / LLM
consumption
- `addExamples(Object...)` - Add example values
**Type Constraints:**
diff --git a/pages/topics/10.06.Marshalling.md
b/pages/topics/10.06.Marshalling.md
index 99305c5736..157b45c2af 100644
--- a/pages/topics/10.06.Marshalling.md
+++ b/pages/topics/10.06.Marshalling.md
@@ -87,18 +87,8 @@ Swaps are associated serializers and parsers registered on a
REST resource via t
...
)
@MarshalledConfig(
- swaps={
- // Calendars should be serialized/parsed as ISO8601 date-time strings
- TemporalCalendarSwap.IsoInstant.class,
-
- // Byte arrays should be serialized/parsed as BASE64-encoded strings
- ByteArraySwap.Base64.class
- },
- beanFilters={
- // Subclasses of MyInterface will be treated as MyInterface objects.
- // Bean properties not defined on that interface will be ignored.
- MyInterface.class
- }
+ calendarFormat=CalendarFormat.ISO_INSTANT, // Calendars serialized/parsed
as ISO8601 date-time strings
+ binaryFormat=BinaryFormat.BASE64 // Byte arrays
serialized/parsed as BASE64-encoded strings
)
public MyResource extends BasicRestServlet {...}
```
diff --git a/pages/topics/10.07.HandlingFormPosts.md
b/pages/topics/10.07.HandlingFormPosts.md
index 9eef9477e4..dca397b227 100644
--- a/pages/topics/10.07.HandlingFormPosts.md
+++ b/pages/topics/10.07.HandlingFormPosts.md
@@ -15,7 +15,7 @@ The following example shows the first approach of handling an
`application/x-www
public static class FormInputBean {
public String aString;
public int aNumber;
- @Swap(TemporalCalendarSwap.IsoLocalDateTime.class)
+ @MarshalledProp(calendarFormat=CalendarFormat.ISO_LOCAL_DATE_TIME)
public Calendar aDate;
}
```
diff --git a/pages/topics/10.08.RestServerComposition.md
b/pages/topics/10.08.RestServerComposition.md
index fbb7b50301..e8a2b5d2c4 100644
--- a/pages/topics/10.08.RestServerComposition.md
+++ b/pages/topics/10.08.RestServerComposition.md
@@ -338,7 +338,7 @@ mixin's own.
A few intentional limitations to be aware of.
-### Mixin scope and per-mixin RestContext (since 9.5.0)
+### Mixin scope and per-mixin RestContext
The mixin walk grafts `@RestOp`-group **methods** from the mixin class. 9.5.0
promotes each
mixin class to its own `RestContext` parent-linked to the host's, so the
following mixin-class
@@ -397,7 +397,7 @@ probe resource, declare it explicitly
(`paths={"/healthz","/healthz/extra/segmen
take whatever path-spec the caller hands them, regardless of the `paths`
attribute on the
servlet class. The auto-mount honor is opt-in — `@Bean Servlet` is the trigger.
-## Runtime-overridable paths (since 9.5.0)
+## Runtime-overridable paths
The `paths` array on `@Rest` is a *default* — apps frequently need to
substitute it at deploy
time without recompiling the resource (e.g. probe URLs that vary by Kubernetes
namespace, internal
diff --git a/pages/topics/10.13.Guards.md b/pages/topics/10.13.Guards.md
index 728da06305..fef978b709 100644
--- a/pages/topics/10.13.Guards.md
+++ b/pages/topics/10.13.Guards.md
@@ -93,4 +93,10 @@ public class MyResource extends BasicRestServlet {
...
}
```
-:::
\ No newline at end of file
+:::
+
+## See also
+
+- [REST Authenticator — Resource-Level
Authentication](/docs/topics/RestServerAuthenticator) — resolves the
`Principal` + roles that `roleGuard` / `RoleBasedRestGuard` check.
+- [AuthN Guards — Bearer / API-Key / JWT](/docs/topics/RestServerAuthGuards)
+- [AuthN Filter Framework — Servlet-Layer
Authentication](/docs/topics/AuthFilterFramework)
\ No newline at end of file
diff --git a/pages/topics/10.38.RestServerAuthGuards.md
b/pages/topics/10.38.RestServerAuthGuards.md
index b20bcb80a2..38f7da3a94 100644
--- a/pages/topics/10.38.RestServerAuthGuards.md
+++ b/pages/topics/10.38.RestServerAuthGuards.md
@@ -192,4 +192,4 @@ public RestGuardList guards(BeanStore bs) {
- **Constant-time comparison.** API-key stores that compare strings naively
are vulnerable to timing attacks. Use `MessageDigest#isEqual`.
- **Replay-attack mitigation.** Bearer tokens and JWTs are bearer tokens by
definition. Pair short `exp` values with TLS pinning and (when feasible) DPoP /
mutual-TLS for sensitive endpoints.
-See also: [Guards](/docs/topics/Guards), [Rate-Limiting and Request-Id
Propagation](/docs/topics/RestServerRateLimitAndRequestId), [AuthN Filter
Framework](/docs/topics/AuthFilterFramework), [SAML 2.0 AuthN
Support](/docs/topics/SamlAuthSupport), [OAuth 2.0 / OIDC AuthN
Support](/docs/topics/OAuthAuthSupport),
[`BasicAdminResource`](/site/apidocs/org/apache/juneau/rest/server/convention/BasicAdminResource.html).
+See also: [Guards](/docs/topics/Guards), [REST Authenticator — Resource-Level
Authentication](/docs/topics/RestServerAuthenticator), [Rate-Limiting and
Request-Id Propagation](/docs/topics/RestServerRateLimitAndRequestId), [AuthN
Filter Framework](/docs/topics/AuthFilterFramework), [SAML 2.0 AuthN
Support](/docs/topics/SamlAuthSupport), [OAuth 2.0 / OIDC AuthN
Support](/docs/topics/OAuthAuthSupport),
[`BasicAdminResource`](/site/apidocs/org/apache/juneau/rest/server/convention/BasicAdmin
[...]
diff --git a/pages/topics/10.42.01.RestServerAuthenticator.md
b/pages/topics/10.42.01.RestServerAuthenticator.md
new file mode 100644
index 0000000000..27d467fd21
--- /dev/null
+++ b/pages/topics/10.42.01.RestServerAuthenticator.md
@@ -0,0 +1,265 @@
+---
+title: "REST Authenticator — Resource-Level Authentication"
+slug: RestServerAuthenticator
+---
+
+The `RestAuthenticator` SPI provides an ergonomic, **in-resource** way to
resolve a request's `Principal` and roles for `@Rest` resources — so
`roleGuard`, `RoleBasedRestGuard`, `@Auth Principal` injection, and
`req.isUserInRole(...)` all "just work" without container-managed security or a
servlet-layer filter.
+
+It **complements** the [AuthN Filter
Framework](/docs/topics/AuthFilterFramework): that framework authenticates at
the servlet-container layer (before Juneau routing); `RestAuthenticator`
authenticates at the resource layer (per matched operation). Both reuse the
**same** underlying auth implementations (SAML, OAuth, JWT, API-key,
bearer-token), so a single bean can serve either layer.
+
+## Resource layer vs. servlet-filter layer
+
+| Aspect | Servlet-filter layer
([`AuthFilterChain`](/docs/topics/AuthFilterFramework)) | Resource layer
(`RestAuthenticator`) |
+|--------|------------------------------------|--------------------------------------|
+| Where it runs | A `jakarta.servlet.Filter`, **before** the `RestServlet` is
reached. | A dedicated framework step **inside** Juneau dispatch, after the
operation is matched and before guards. |
+| Registration | `@Bean AuthFilterChain` auto-mounted by
`JettyServerComponent`. | Resource bean / `@Rest(authenticator=)` / `@Bean`
method / `RestServlet.createAuthenticator(...)`. |
+| Scope | URL patterns across the whole servlet. | Per `@Rest` resource,
inherited down the child-resource tree. |
+| Unmatched (404) paths | Filter still runs. | Authenticator runs only for
matched operations. |
+| Underlying contract | `Authenticator` (`AuthFilter`, `AuthFilterChain`). |
`RestAuthenticator` (adapts any `Authenticator`). |
+
+Both layers ultimately produce the same thing — a resolved `Principal` + role
set surfaced through `getUserPrincipal()` / `isUserInRole(String)` /
`getRemoteUser()` and the `RestServerConstants.PRINCIPAL_ATTR` request
attribute — so downstream guards and `@Auth` resolution don't care which layer
authenticated the request.
+
+## The `Authenticator` contract
+
+The lowest-level contract is shared by both layers:
+
+```java
+@FunctionalInterface
+public interface Authenticator {
+ Optional<AuthResult> authenticate(HttpServletRequest req) throws
AuthenticationException;
+}
+```
+
+The same three-state return contract as `AuthFilter`:
+
+| State | Meaning |
+|-------|---------|
+| `Optional.empty()` | Does not apply — no recognizable credentials. |
+| `Optional.of(AuthResult)` | Authentication succeeded. |
+| throw `AuthenticationException` | Credentials present but invalid (→ `401`).
|
+
+`AuthFilter` and `AuthFilterChain` both **implement `Authenticator`**, so
every existing filter (`BearerTokenAuthFilter`, `ApiKeyAuthFilter`,
`SamlAuthFilter`, `OAuthFilter`, `OidcSessionAuthFilter`, custom impls) is
reusable verbatim at the resource layer.
+
+## The `RestAuthenticator` SPI
+
+`RestAuthenticator` is the resource-level front door. It operates on
`RestRequest` (which **is-a** `HttpServletRequest`), so adapting an existing
`Authenticator` is a one-liner:
+
+```java
+public abstract class RestAuthenticator {
+
+ // empty = anonymous/passthrough, present = success, throw = 401.
+ public abstract Optional<AuthResult> authenticate(RestRequest req) throws
AuthenticationException;
+
+ // Adapts any Authenticator (AuthFilter / AuthFilterChain) into a
RestAuthenticator.
+ public static RestAuthenticator of(Authenticator a) { ... }
+}
+```
+
+A custom authenticator can wrap a reusable filter bean directly:
+
+```java
+// Reuse the SAML filter bean at the resource layer.
+public class SamlRestAuthenticator extends RestAuthenticator {
+ private final SamlAuthFilter filter;
+
+ public SamlRestAuthenticator(SamlAuthFilter filter) { // constructor
injection
+ this.filter = filter;
+ }
+
+ @Override
+ public Optional<AuthResult> authenticate(RestRequest req) {
+ return filter.authenticate(req);
+ }
+}
+```
+
+There is intentionally **no** separate roles-only authenticator subclass —
that need is folded into the `AuthResult` merge modes below.
+
+## `AuthResult` merge modes
+
+`AuthResult` carries an immutable merge mode that controls how its `Principal`
+ roles combine with results accumulated earlier in the chain:
+
+| Factory | Mode | Principal | Effect |
+|---------|------|-----------|--------|
+| `AuthResult.of(principal, roles...)` | `ADD` | required | Union these roles
into the accumulated set; first non-null principal wins. |
+| `AuthResult.ofRoles(roles...)` | `ADD` | `null` | Roles-only augmentation —
keep the inherited/existing principal, just add roles. |
+| `AuthResult.replacing(principal, roles...)` | `REPLACE` | required | Discard
everything accumulated so far; this principal + roles win outright. |
+
+```java
+// ADD (default): contribute identity + roles.
+AuthResult.of(principal, "admin", "user");
+
+// ADD, roles-only: keep whoever authenticated upstream, just add a role.
+AuthResult.ofRoles("reader");
+
+// REPLACE: swap identity entirely (e.g. JWT instead of an inherited SAML
identity).
+AuthResult.replacing(jwtPrincipal, "admin");
+```
+
+The existing `AuthResult.of(...)` factories are unchanged and default to
`ADD`, so `AuthFilterChain`'s long-standing union semantics are preserved.
+
+## Registration & dependency injection
+
+A resource's `RestAuthenticator` is resolved with the following precedence
(highest first), mirroring how `guards` / `converters` / `encoders` resolve and
memoized per `RestContext`:
+
+1. **Registered bean** — `bs.getBean(RestAuthenticator.class)`.
+2. **`@Bean` factory method** on the resource.
+3. **`@Rest(authenticator = MyAuthenticator.class)`** — instantiated via the
bean store (constructor injection).
+4. **Overridable method** — `protected RestAuthenticator
createAuthenticator(BeanStore bs)` on `RestServlet` (returns `null` by default).
+
+All four paths run through the resource's bean store, and because child bean
stores delegate to the parent, both injection scenarios below work with no
special handling.
+
+### (a) Inject an `Authenticator` into a `RestAuthenticator`
+
+```java
+@Bean
+public SamlAuthFilter samlFilter(SamlAssertionValidator v) {
+ return SamlAuthFilter.create().validator(v).build();
+}
+
+public class SamlRestAuthenticator extends RestAuthenticator {
+ private final SamlAuthFilter filter;
+ public SamlRestAuthenticator(SamlAuthFilter filter) { this.filter =
filter; } // ctor injection
+
+ @Override
+ public Optional<AuthResult> authenticate(RestRequest req) { return
filter.authenticate(req); }
+}
+
+@Rest(authenticator = SamlRestAuthenticator.class)
+public class SecuredResource extends BasicRestServlet { ... }
+```
+
+### (b) Inject a `RestAuthenticator` into a REST resource
+
+```java
+@Rest(children = { ... })
+public class RootResource extends BasicRestServlet {
+
+ @Bean
+ public RestAuthenticator authenticator(AuthFilterChain chain) { // chain
injected
+ return RestAuthenticator.of(chain);
+ }
+}
+```
+
+A `RestAuthenticator` (or `Authenticator`) bean registered on the root or any
ancestor is visible to every descendant resource's bean store through the
parent-delegating chain.
+
+To declaratively unset an inherited annotation value, the
`@Rest(authenticator=...)` attribute defaults to the sentinel
`RestAuthenticator.Null` ("not specified").
+
+## Inheritance & folding across child resources
+
+This is a **deliberate divergence** from guards (which are child-isolated).
Authenticators **inherit down the child-resource tree**. At request time, for a
target resource `D`, the framework:
+
+1. Walks the child-resource chain root → … → `D`, collecting each node's
resolved authenticator (skipping nodes that declared none). A node carrying
`noInherit={"authenticator"}` truncates the walk above it — the subtree opts
out and becomes anonymous.
+2. Runs the collected authenticators in order (root first), folding each
`Optional<AuthResult>`:
+ - empty → skip (doesn't apply);
+ - `ADD` → keep the current principal if one is already set (else adopt this
result's principal, which may be `null`), and **union roles**;
+ - `REPLACE` → reset the accumulator to this result's principal + roles;
+ - throw `AuthenticationException` → record the failure.
+3. After folding: if a principal/roles were resolved, apply them to the
request. If nothing succeeded but at least one threw → aggregated `401`. If
everything was empty → anonymous passthrough.
+
+### Root covers all
+
+A single authenticator on the **root** resource covers every descendant —
child resources that declare nothing simply inherit it:
+
+```java
+@Rest(children = { ChildResource.class })
+public class RootResource extends BasicRestServlet {
+ @Bean
+ public RestAuthenticator authenticator(AuthFilterChain chain) {
+ return RestAuthenticator.of(chain); // covers RootResource +
ChildResource + ...
+ }
+}
+
+@Rest(path = "/child")
+public class ChildResource extends BasicRestServlet {
+ @RestGet(path = "/whoami", roleGuard = "admin")
+ public String whoami(@Auth Principal p) { return p.getName(); } //
identity inherited from root
+}
+```
+
+### Descendant augments (adds roles)
+
+A child can contribute extra roles while keeping the inherited identity by
returning an `ADD` result with a `null` principal:
+
+```java
+@Rest(path = "/childAdd", authenticator = AddRoleAuthenticator.class)
+public class ChildAddResource extends BasicRestServlet { ... }
+
+public class AddRoleAuthenticator extends RestAuthenticator {
+ @Override
+ public Optional<AuthResult> authenticate(RestRequest req) {
+ return Optional.of(AuthResult.ofRoles("extra")); // keep root
principal, add "extra"
+ }
+}
+```
+
+### Descendant replaces (swaps identity)
+
+A child can swap the inherited identity entirely (the "JWT instead of SAML"
case) with a `REPLACE` result:
+
+```java
+@Rest(path = "/childReplace", authenticator = JwtReplaceAuthenticator.class)
+public class ChildReplaceResource extends BasicRestServlet { ... }
+
+public class JwtReplaceAuthenticator extends RestAuthenticator {
+ @Override
+ public Optional<AuthResult> authenticate(RestRequest req) {
+ var jwtPrincipal = verifyJwt(req); // throws AuthenticationException
on bad token
+ return Optional.of(AuthResult.replacing(jwtPrincipal, "admin")); //
discard inherited identity
+ }
+}
+```
+
+### Subtree opt-out with `noInherit`
+
+A descendant can cut off inherited authenticators for its subtree — useful for
a public area under an otherwise-secured root:
+
+```java
+@Rest(path = "/public", noInherit = { "authenticator" })
+public class PublicResource extends BasicRestServlet {
+ @RestGet(path = "/ping") // no inherited auth; no roleGuard →
anonymous passthrough
+ public String ping() { return "pong"; }
+}
+```
+
+## Request-time wiring
+
+The folded `(principal, roles)` is stored on the `RestRequest`, which
overrides `getUserPrincipal()` / `isUserInRole(String)` / `getRemoteUser()` to
consult the stored result (falling back to the wrapped request when the stored
value is `null`), and stashes the principal under
`RestServerConstants.PRINCIPAL_ATTR` so `@Auth Principal` injection finds it.
This mirrors exactly what `AuthenticatedRequestWrapper` overrides at the filter
layer, so existing code works unchanged:
+
+```java
+@Rest(authenticator = MyAuthenticator.class)
+public class ApiResource extends BasicRestServlet {
+
+ @RestGet(path = "/me", roleGuard = "admin") // roleGuard sees
resolved roles
+ public String me(@Auth Principal p) { // @Auth sees
resolved principal
+ return p.getName();
+ }
+
+ @RestGet(path = "/check")
+ public boolean check(RestRequest req) {
+ return req.isUserInRole("admin"); // isUserInRole works
unchanged
+ }
+}
+```
+
+The authenticator fold runs **once per request, before guards** (so
`roleGuard` sees resolved roles) and before op-argument resolution (so `@Auth`
sees the principal). Concretely it is a dedicated framework step invoked
immediately before `@RestPreCall` hooks, giving this deterministic order:
+
+1. find operation / create the op session;
+2. **authenticate fold** → annotate the `RestRequest`;
+3. `@RestPreCall` hooks (also see the resolved principal/roles);
+4. guard loop (`roleGuard` sees roles) → op invocation (`@Auth` sees
principal);
+5. `@RestPostCall`.
+
+Because the fold runs per matched operation, unmatched (404) paths are never
authenticated. Already-authenticated requests (container security or a
servlet-layer `AuthFilterChain`) are **augmented** by default — an `ADD` result
keeps the existing principal and unions roles; a `REPLACE` result overrides.
+
+## See also
+
+- [Guards](/docs/topics/Guards) — declarative class/method access control via
`@Rest(guards/roleGuard)`.
+- [AuthN Guards — Bearer / API-Key / JWT](/docs/topics/RestServerAuthGuards) —
op-level AuthN guards + `@Auth Principal` resolution.
+- [AuthN Filter Framework — Servlet-Layer
Authentication](/docs/topics/AuthFilterFramework) — the servlet-container-layer
peer this complements.
+- [SAML 2.0 AuthN Support](/docs/topics/SamlAuthSupport) — reusable
`SamlAuthFilter` implementation.
+- [OAuth 2.0 / OIDC AuthN Support](/docs/topics/OAuthAuthSupport) — reusable
`OAuthFilter` implementation.
+-
[`RestAuthenticator`](/site/apidocs/org/apache/juneau/rest/server/auth/RestAuthenticator.html)
+-
[`Authenticator`](/site/apidocs/org/apache/juneau/rest/server/auth/Authenticator.html)
+-
[`AuthResult`](/site/apidocs/org/apache/juneau/rest/server/auth/AuthResult.html)
diff --git a/pages/topics/10.42.AuthFilterFramework.md
b/pages/topics/10.42.AuthFilterFramework.md
index ebbeb60b6d..29b73d2b38 100644
--- a/pages/topics/10.42.AuthFilterFramework.md
+++ b/pages/topics/10.42.AuthFilterFramework.md
@@ -128,10 +128,11 @@ Example: Bearer JWT grants `user`, API key grants
`admin`. Both succeed on the s
## Registration via `@Bean`
-The preferred pattern mirrors FINISHED-69's `@Bean RestGuardList` idiom:
+The preferred pattern mirrors FINISHED-69's `@Bean RestGuardList` idiom. Since
`JettyMicroservice` is `final`, place the `@Bean` method in a separate
`@Configuration` class:
```java
-public class MyMicroservice extends JettyMicroservice {
+@Configuration
+public class MyAppConfig {
@Bean
public AuthFilterChain authFilters(BeanStore bs) {
@@ -149,6 +150,14 @@ public class MyMicroservice extends JettyMicroservice {
}
```
+Then wire the configuration class at startup:
+
+```java
+var beanStore = new BasicBeanStore();
+beanStore.addBean(Servlet.class, new RootResources());
+JettyMicroservice.run(args, beanStore, true, JettyConfiguration.class,
MyAppConfig.class);
+```
+
`JettyServerComponent.onStart(...)` scans the `BeanStore` for an
`AuthFilterChain` bean and registers it at `/*` before any servlet is mounted.
## Direct filter registration
@@ -231,6 +240,7 @@ Spring Security's
[`SecurityFilterChain`](https://docs.spring.io/spring-security
## See also
+- [REST Authenticator — Resource-Level
Authentication](/docs/topics/RestServerAuthenticator) — the resource-layer peer
that resolves identity + roles inside Juneau dispatch, reusing these same
filters.
- [AuthN Guards — Bearer / API-Key / JWT](/docs/topics/RestServerAuthGuards) —
the FINISHED-69 op-level guards that compose with this framework.
- [SAML 2.0 AuthN Support](/docs/topics/SamlAuthSupport) — the opt-in
`juneau-rest-server-auth-saml` module that adds a `SamlAuthFilter`
implementation.
- [OAuth 2.0 / OIDC AuthN Support](/docs/topics/OAuthAuthSupport) — the opt-in
`juneau-rest-server-auth-oauth` module that adds an `OAuthFilter` +
introspection / OIDC discovery / grant-flow helpers.
diff --git a/pages/topics/12.01.JuneauRestServerSpringbootBasics.md
b/pages/topics/12.01.JuneauRestServerSpringbootBasics.md
index 9b03acbe58..caf4bf032f 100644
--- a/pages/topics/12.01.JuneauRestServerSpringbootBasics.md
+++ b/pages/topics/12.01.JuneauRestServerSpringbootBasics.md
@@ -30,7 +30,7 @@ org.apache.juneau.rest.server.springboot_0.0.0.jar
The `juneau-rest-server-springboot` library provides classes to make it easy
to integrate Juneau REST resources with
Spring and Spring Boot.
-#### Bean Precedence (since 9.5.0)
+#### Bean Precedence
When a Juneau REST resource runs inside a Spring Boot application,
framework-managed beans on `RestContext`
(`CallLogger`, `EncoderSet`, `SerializerSet`, `ParserSet`, `ThrownStore`,
`Config`, `VarResolver`,
diff --git a/sidebars.ts b/sidebars.ts
index 5711a1b84d..8c6698fadb 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -643,43 +643,66 @@ const sidebars: SidebarsConfig = {
},
],
},
- {
- type: 'category',
- label: '2.34. TOML
Support',
- collapsed: true,
- items: [
- {
- type:
'doc',
- id:
'topics/02.34.01.TomlBasics',
- label:
'2.34.1. TOML Basics',
- },
- ],
- },
- {
- type: 'category',
- label: '2.34.5.
Protobuf Text Format',
- collapsed: true,
- items: [
- {
- type:
'doc',
- id:
'topics/02.34.05.ProtobufBasics',
- label:
'2.34.5.1. Protobuf Text Format Basics',
- },
- {
- type:
'doc',
- id:
'topics/02.34.07.ProtobufBinaryBasics',
- label:
'2.34.5.2. Protobuf Binary Format Basics',
- },
- {
- type:
'doc',
- id:
'topics/02.34.06.ParquetBasics',
- label:
'2.34.6. Parquet Basics',
- },
- ],
- },
- {
- type: 'category',
- label: '2.38. JSONL
Support',
+ {
+ type: 'category',
+ label: '2.34. TOML Support',
+ collapsed: true,
+ items: [
+ {
+ type: 'doc',
+ id:
'topics/02.34.01.TomlBasics',
+ label: '2.34.1.
TOML Basics',
+ },
+ {
+ type: 'doc',
+ id:
'topics/02.34.05.ProtobufBasics',
+ label: '2.34.2.
Protobuf Text Format Basics',
+ },
+ {
+ type: 'doc',
+ id:
'topics/02.34.07.ProtobufBinaryBasics',
+ label: '2.34.3.
Protobuf Binary Format Basics',
+ },
+ {
+ type: 'doc',
+ id:
'topics/02.34.06.ParquetBasics',
+ label: '2.34.4.
Parquet Basics',
+ },
+ ],
+ },
+ {
+ type: 'category',
+ label: '2.35. YAML Support',
+ collapsed: true,
+ items: [
+ { type: 'doc', id:
'topics/02.35.01.YamlBasics', label: '2.35.1. YAML Basics' },
+ { type: 'doc', id:
'topics/02.35.02.YamlMethodology', label: '2.35.2. YAML Methodology' },
+ { type: 'doc', id:
'topics/02.35.03.YamlSerializers', label: '2.35.3. YAML Serializers' },
+ { type: 'doc', id:
'topics/02.35.04.YamlParsers', label: '2.35.4. YAML Parsers' },
+ { type: 'doc', id:
'topics/02.35.05.YamlAnnotation', label: '2.35.5. @YamlConfig Annotation' },
+ ],
+ },
+ {
+ type: 'category',
+ label: '2.36. CSV Support',
+ collapsed: true,
+ items: [
+ { type: 'doc', id:
'topics/02.36.01.CsvBasics', label: '2.36.1. CSV Basics' },
+ { type: 'doc', id:
'topics/02.36.02.CsvSerializers', label: '2.36.2. CSV Serializers' },
+ { type: 'doc', id:
'topics/02.36.03.CsvParsers', label: '2.36.3. CSV Parsers' },
+ ],
+ },
+ {
+ type: 'category',
+ label: '2.37. Markdown Support',
+ collapsed: true,
+ items: [
+ { type: 'doc', id:
'topics/02.37.01.MarkdownBasics', label: '2.37.1. Markdown Basics' },
+ ],
+ },
+ {
+ type: 'category',
+ label: '2.38. JSONL Support',
collapsed: true,
items: [
{
@@ -771,39 +794,9 @@ const sidebars: SidebarsConfig = {
},
],
},
- {
- type: 'category',
- label: '2.35. YAML
Support',
- collapsed: true,
- items: [
- { type: 'doc',
id: 'topics/02.35.01.YamlBasics', label: '2.35.1. YAML Basics' },
- { type: 'doc',
id: 'topics/02.35.02.YamlMethodology', label: '2.35.2. YAML Methodology' },
- { type: 'doc',
id: 'topics/02.35.03.YamlSerializers', label: '2.35.3. YAML Serializers' },
- { type: 'doc',
id: 'topics/02.35.04.YamlParsers', label: '2.35.4. YAML Parsers' },
- { type: 'doc',
id: 'topics/02.35.05.YamlAnnotation', label: '2.35.5. @YamlConfig Annotation' },
- ],
- },
- {
- type: 'category',
- label: '2.36. CSV
Support',
- collapsed: true,
- items: [
- { type: 'doc',
id: 'topics/02.36.01.CsvBasics', label: '2.36.1. CSV Basics' },
- { type: 'doc',
id: 'topics/02.36.02.CsvSerializers', label: '2.36.2. CSV Serializers' },
- { type: 'doc',
id: 'topics/02.36.03.CsvParsers', label: '2.36.3. CSV Parsers' },
- ],
- },
- {
- type: 'category',
- label: '2.37. Markdown
Support',
- collapsed: true,
- items: [
- { type: 'doc',
id: 'topics/02.37.01.MarkdownBasics', label: '2.37.1. Markdown Basics' },
- ],
- },
- {
- type: 'category',
- label: '2.50. Token /
Record Streaming',
+ {
+ type: 'category',
+ label: '2.50. Token / Record
Streaming',
collapsed: true,
items: [
{ type: 'doc',
id: 'topics/02.50.01.TokenStreamingBasics', label: '2.50.1. Token-Streaming
Basics' },
@@ -1708,6 +1701,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.42.AuthFilterFramework',
label: '10.42. AuthN
Filter Framework — Servlet-Layer Authentication',
},
+ {
+ type: 'doc',
+ id:
'topics/10.42.01.RestServerAuthenticator',
+ label: '10.42.01. REST
Authenticator — Resource-Level Authentication',
+ },
{
type: 'doc',
id:
'topics/10.43.SamlAuthSupport',