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 2deecc264e SVL overhaul docs: #{...} scripting + VarTemplate + @Value
Supplier<String>
2deecc264e is described below
commit 2deecc264e857dae54857509ebbd8c51b7d3ec51
Author: James Bognar <[email protected]>
AuthorDate: Tue May 26 08:33:53 2026 -0400
SVL overhaul docs: #{...} scripting + VarTemplate + @Value Supplier<String>
Companion to the juneau-side SVL overhaul commit:
- Release-notes section in 9.5.0 covering the #{...} scripting syntax,
the ~68-function built-in catalog, hard-break removal of 11 transformation
Var classes, the new VarTemplate compilation API, @Value Supplier<String>
field-type support, and stable-value folding.
- Topic-page rewrite for SimpleVariableLanguageBasics covering #{...}
syntax, VarFunction SPI, type coercion, VarTemplate, and the
Patterns-emerging-from-composition showcase.
- ValueAnnotationBasics extended with Supplier<String> re-evaluating-reads
section.
- SvlVariables trimmed to remove rows for the 11 retired Vars + migration
note pointing at the #{...} replacements.
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 159 +++++++++++++++
.../02.21.01.SimpleVariableLanguageBasics.md | 224 +++++++++++++++++++--
pages/topics/02.21.06.ValueAnnotationBasics.md | 64 +++++-
pages/topics/10.13.SvlVariables.md | 13 +-
4 files changed, 435 insertions(+), 25 deletions(-)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 5a9b3663c3..551adbb311 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -411,6 +411,165 @@ land, parser sessions other than `JsonParserSession` /
`Json5ParserSession` prod
- `Optional<T>` field/parameter injection collapses `null` and empty-string
results to
`Optional.empty()` — matching the "missing-key" intuition that consumers
expect.
+#### SVL overhaul: `#{...}` scripting + `VarTemplate` compilation (TODO-102 +
TODO-103)
+
+This is a **major-release breaking change** in the SVL surface. The 11
single-purpose
+transformation `Var` classes are removed without `@Deprecated` shims; their
behaviour is
+recovered through the new `#{name(args)}` function-call syntax.
+
+##### New `#{...}` function-call syntax
+
+- Introduced the `#{name(arg1, arg2, ...)}` script-call form inside any SVL
string.
+ Tokenized by the same recursive-descent compiler that handles `${...}` and
`$X{...}`, so the
+ three forms nest naturally (`#{upper(${user.name})}`,
`${prefix:#{lower(${value})}}`, …).
+- Added the `org.apache.juneau.commons.svl.VarFunction` SPI plus
`TypedFunction` reflection
+ helper. `TypedFunction` derives arity and per-arg types from the declared
`invoke(...)` method
+ signature(s) and routes string args through `ArgCoercer` for coercion to
`int` / `long` /
+ `double` / `boolean` / `String` / `String[]` / `Object`.
+- Boolean truthiness is centralised in `ArgCoercer`: `"true"`, `"yes"`,
`"on"`, `"1"` are truthy;
+ everything else (including `null` and empty string) is falsy. `#{if(...)}`,
`#{not(...)}`,
+ `#{switch(...)}`, etc. all share the same rule.
+- `#{switch(...)}` matches each case pattern as a glob (`*` = any sequence,
`?` = single char),
+ preserving the legacy `$SW{...}` Var's matching semantics.
+- `#{len(s, delimiter)}` adds a two-argument overload that counts parts after
split-by-delimiter,
+ preserving the legacy `$LN{s, delim}` Var's two-arg behaviour. The
single-arg form still
+ returns character length.
+
+##### Built-in function catalog (~68 functions, 10 categories)
+
+| Category | Examples |
+|---|---|
+| String | `upper`, `lower`, `len`, `substring`, `trim`, `split`, `join`,
`format`, `concat`, `pathToken` |
+| Type conversion | `toInt`, `toLong`, `toDouble`, `toBoolean`, `toString` |
+| Arithmetic | `add`, `sub`, `mul`, `div`, `mod`, `min`, `max`, `abs`,
`round`, `floor`, `ceil` |
+| Boolean | `not`, `and`, `or`, `xor`, `eq`, `ne`, `lt`, `gt`, `le`, `ge` |
+| Conditional | `if`, `switch`, `coalesce`, `notEmpty`, `case` |
+| Regex | `matches`, `replace`, `extract` |
+| Encoding | `urlEncode`, `urlDecode`, `base64Encode`, `base64Decode`,
`htmlEscape`, `htmlUnescape` |
+| Date/time | `now`, `today`, `formatDate`, `parseDate` |
+| Random/UUID | `uuid`, `randomInt`, `randomLong`, `randomDouble`,
`randomString`, `randomChar` |
+| JSON navigation | `jsonPath`, `get`, `keys`, `values`, `size` |
+
+See <a
href="/site/apidocs/org/apache/juneau/commons/svl/functions/package-summary.html"
target="_blank">org.apache.juneau.commons.svl.functions</a>
+for the per-class function lists with full signatures and arg style notes.
+
+##### Function discovery
+
+Three discovery channels, evaluated in order:
+
+1. **Explicit registration** —
`VarResolver.create().functions(MyFn.class).build()`.
+2. **`ServiceLoader` discovery** — declare your implementation in
+ `META-INF/services/org.apache.juneau.commons.svl.VarFunction`.
`VarResolver.create().defaultFunctions().build()`
+ and `VarResolver.DEFAULT` both load these automatically.
+3. **`BeanStore` lookup** — built-in functions are constructed through the
+ `VarResolver.Builder`'s `BeanStore` so they can themselves take constructor
`@Inject`/`@Value`
+ dependencies (rarely needed but available).
+
+Unknown function names fail lazily at resolve time (`No such function 'foo'`)
rather than at
+compile time, matching the existing `Var` registry behaviour.
+
+##### Hard-break removal of 11 `Var` classes
+
+The following 11 transformation `Var` classes are **removed without
`@Deprecated` shims**:
+
+| Removed `Var` | Removed prefix | Replacement |
+|---|---|---|
+| `IfVar` | `$IF{...}` | `#{if(cond, then, else)}` |
+| `SwitchVar` | `$SW{...}` | `#{switch(value, pat1:result1, ..., *:default)}`
(glob semantics preserved) |
+| `CoalesceVar` | `$CO{...}` | `#{coalesce(a, b, c, ...)}` |
+| `NotEmptyVar` | `$NE{...}` | `#{notEmpty(arg)}` |
+| `PatternMatchVar` | `$PM{...}` | `#{matches(value, pattern)}` |
+| `PatternReplaceVar` | `$PR{...}` | `#{replace(value, pattern, replacement)}`
|
+| `PatternExtractVar` | `$PE{...}` | `#{extract(value, pattern, groupIndex)}` |
+| `UpperCaseVar` | `$UC{...}` | `#{upper(arg)}` |
+| `LowerCaseVar` | `$LC{...}` | `#{lower(arg)}` |
+| `LenVar` | `$LN{...}` | `#{len(arg)}` (single-arg) or `#{len(arg,
delimiter)}` (two-arg parity) |
+| `SubstringVar` | `$ST{...}` | `#{substring(arg, start)}` or
`#{substring(arg, start, end)}` |
+
+`VarList.addDefault()` no longer registers the removed Vars; the
+`org.apache.juneau.commons.svl.vars` package no longer ships their classes.
Any user code or
+template that still references the removed prefixes fails at resolve time with
+`No such var '<prefix>'`.
+
+The source-data Vars (`$E{}`, `$S{}`, `$MF{}`, `$A{}`, `$P{}` and the shortcut
`${}`) are
+unchanged.
+
+##### New `VarTemplate` compiled-template API (TODO-103)
+
+- Added `VarResolver.compile(String)` → `VarTemplate` — tokenizes a template
once and returns a
+ reusable, threadsafe compiled form. Subsequent `template.resolve(session)`
calls skip
+ tokenization + var-registry lookup, walking the cached segment array
directly.
+- Added `VarResolver.resolveSupplier(String)` → `Supplier<String>` — opens a
fresh session on
+ every `.get()` call. Safe to share across threads. Recommended default.
+- Added `VarResolverSession.compile(String)` and
`VarResolverSession.resolveSupplier(String)` —
+ symmetric session-bound variants. The session-bound Supplier inherits the
session's
+ threadsafety contract (i.e. not threadsafe by default).
+- Added `VarTemplate.isLiteral()` — `true` when the template resolves to a
fixed string (empty
+ template, no variables, or all variables fold to literals via stable-value
folding). Callers
+ can fast-path purely-literal inputs.
+- Refactored `VarResolverSession.resolve(String)` to delegate to
+ `compile(input).resolve(session)` so there is a single tokenizer +
segment-resolution path in
+ the codebase. No behaviour change for existing callers.
+
+##### `@Value Supplier<String>` field-type autodetect
+
+- `BeanInstantiator` now inspects the declared field/parameter type at
injection sites carrying
+ `@Value`. A `Supplier<String>` field receives a re-evaluating, threadsafe
Supplier (delegates
+ to `VarTemplate.asSupplierWithFreshSessions(...)`); a bare `String` field
gets the one-shot
+ resolved value as before. **No `@Value(supplier=true)` flag is required —
the field type IS
+ the opt-in signal.**
+- Literal-only expressions (`@Value("plain") Supplier<String>`) take a
constant-folding fast
+ path that returns the same captured `String` reference on every `.get()`
call, avoiding the
+ per-call session allocation entirely.
+- Worked patterns for live UUID factories, idempotency keys, jittered delays,
and hot-reload
+ config knobs are in the [`@Value` topic
page](/docs/topics/ValueAnnotationBasics#supplierstring-field-type--re-evaluating-reads-950).
+
+##### Compile-time stable-value folding
+
+- New `default boolean Var.isStable() { return false; }` SPI hook. Opt-in per
`Var`
+ implementation; conservative `false` default protects third-party `Var`s
from accidental
+ incorrect folding.
+- Four built-in `Var`s opt in: `EnvVariablesVar`, `SystemPropertiesVar`,
`ManifestFileVar`,
+ `ArgsVar`. Compiled templates eagerly resolve these at compile time and
replace the segment
+ with a `LiteralSegment` — runtime dispatch is eliminated.
+- `SystemPropertiesVar`'s opt-in carries a documented caveat:
`System.setProperty(...)` calls
+ *after* compile do not propagate to a folded template. In practice this is
the desired
+ behaviour for the common "read once at startup" use case.
+- `ConfigVar`, `PropertyVar`, `DotenvVar`, `EnvFileVar` stay non-stable. Their
backing source
+ can mutate at runtime (config reload, file watch, runtime `Settings.set`),
so folding would
+ produce stale reads.
+
+##### Internal perf
+
+- `BeanInstantiator`'s `@Value` resolution path caches a `VarTemplate` per
distinct expression
+ in `ValueResolver.TEMPLATE_CACHE`. Repeated bean construction reads the
cached form rather
+ than re-tokenizing.
+- `RestOpContext.pathMatchers` Memoizer now goes through
`vr.compile(p).resolve(session)`
+ explicitly so the framework hot loop exercises the compiled-template seam.
If we ever switch
+ to per-request dynamic path resolution, only the `.resolve(session)` step
moves to the
+ request handler — the compile is already done at context-build time.
+- Micro-benchmark (`juneau-utest/.../VarResolver_Benchmark_Test`):
compile-once + resolve-N is
+ ~1.7× faster than compile-per-call for plain `${...}` templates and ~1.4× for
+ `#{...}` script templates on a representative machine. The headline `≥ 2× /
≥ 5×` targets
+ from the plan apply to comparisons against the pre-9.5 legacy ad-hoc
dispatcher, which is no
+ longer in the tree — the unified compiled-form path is now the baseline.
+
+##### Migration
+
+For any SVL template that uses one of the 11 removed `Var` prefixes, rewrite
to `#{...}`. Cookbook:
+
+| Before | After |
+|---|---|
+| `$UC{$S{user.name}}` | `#{upper(${user.name})}` |
+| `$IF{$S{prod},prod.key,dev.key}` | `#{if(${prod}, prod.key, dev.key)}` |
+| `$SW{$S{os},*win*:Windows,*:Unix}` | `#{switch(${os}, *win*:Windows,
*:Unix)}` |
+| `$PR{$S{name},[aeiou],*}` | `#{replace(${name}, [aeiou], *)}` |
+| `$LN{a,b,c,,}` | `#{len(a,b,c, ,)}` |
+| `$ST{hello,2,4}` | `#{substring(hello, 2, 4)}` |
+
+The full `${...}` shortcut, `$S{}`/`$E{}`/`$MF{}`/`$A{}`/`$P{}` source vars,
and `$C{}` config
+var keep their existing syntax — no migration required for those.
+
### juneau-config
#### Classpath-default `Config` bridge to `Settings` (TODO-79)
diff --git a/pages/topics/02.21.01.SimpleVariableLanguageBasics.md
b/pages/topics/02.21.01.SimpleVariableLanguageBasics.md
index 7f240d5816..b53365f0c6 100644
--- a/pages/topics/02.21.01.SimpleVariableLanguageBasics.md
+++ b/pages/topics/02.21.01.SimpleVariableLanguageBasics.md
@@ -15,28 +15,21 @@ themselves contain more variables.
The <a href="/site/apidocs/org/apache/juneau/commons/svl/VarResolver.html"
target="_blank">VarResolver</a> class (in module `juneau-commons`) is used to
resolve variables.
The <a
href="/site/apidocs/org/apache/juneau/commons/svl/VarResolver.html#DEFAULT"
target="_blank">VarResolver.DEFAULT</a> resolver is a reusable instance
-of this class configured with the following basic variables:
+of this class configured with the following basic source variables:
<tree>
<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.html"
target="_blank">SystemPropertiesVar</a></java-class> -
`$S{key[,default]}`</node-0>
<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/EnvVariablesVar.html"
target="_blank">EnvVariablesVar</a></java-class> - `$E{key[,default]}`</node-0>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/ArgsVar.html"
target="_blank">ArgsVar</a></java-class> - `$A{key[,default]}`</node-0>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/ManifestFileVar.html"
target="_blank">ManifestFileVar</a></java-class> - `$MF{key[,default]}`</node-0>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/PropertyVar.html"
target="_blank">PropertyVar</a></java-class> - `$P{key[,default]}` (the unified
`Settings` source — see `@Value` topic)</node-0>
</tree>
-The following logic variables are also provided:
-
-<tree>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/IfVar.html"
target="_blank">IfVar</a></java-class> - `$IF{arg,then[,else]}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/SwitchVar.html"
target="_blank">SwitchVar</a></java-class> -
`$SW{arg,pattern1:then1[,pattern2:then2...]}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/CoalesceVar.html"
target="_blank">CoalesceVar</a></java-class> - `$CO{arg1[,arg2...]}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/PatternMatchVar.html"
target="_blank">PatternMatchVar</a></java-class> - `$PM{arg,pattern}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/PatternReplaceVar.html"
target="_blank">PatternReplaceVar</a></java-class> -
`$PR{arg,pattern,replace}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/PatternExtractVar.html"
target="_blank">PatternExtractVar</a></java-class> -
`$PE{arg,pattern,groupIndex}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/NotEmptyVar.html"
target="_blank">NotEmptyVar</a></java-class> - `$NE{arg}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/UpperCaseVar.html"
target="_blank">UpperCaseVar</a></java-class> - `$UC{arg}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/LowerCaseVar.html"
target="_blank">LowerCaseVar</a></java-class> - `$LC{arg}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/LenVar.html"
target="_blank">LenVar</a></java-class> - `$LN{arg[,delimiter]}`</node-0>
-<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/SubstringVar.html"
target="_blank">SubstringVar</a></java-class> - `$ST{arg,start[,end]}`</node-0>
-</tree>
+Conditional/transformation logic that used to live in dedicated `Var` classes
(`$UC{}` upper-case,
+`$IF{}` ternary, `$SW{}` switch, etc.) has moved to the `#{...}` function-call
syntax — see
+[`#{...}` function-call syntax (9.5.0)](#-function-call-syntax-950) below. The
full migration table
+of the 11 removed `Var` classes and their `#{...}` replacements is in the
[9.5.0 release
+notes](../release-notes/9.5.0.md).
:::tip Example
```java
@@ -58,7 +51,7 @@ String property =
VarResolver.DEFAULT.resolve("$E{MYPROPERTY,$S{my.property,not
## The `${xxx}` shortcut (9.5.0)
-Starting in 9.5.0 the tokenizer recognises `${xxx}` as a shorthand for
`$P{xxx}` — the
+The tokenizer recognises `${xxx}` as a shorthand for `$P{xxx}` — the
<a href="/site/apidocs/org/apache/juneau/commons/svl/vars/PropertyVar.html"
target="_blank">PropertyVar</a>
that resolves against the unified `Settings` source stack (system properties,
env vars,
classpath `juneau.cfg`, per-microservice `Config`, Spring `Environment`, …). A
Spring-style
@@ -73,3 +66,200 @@ VarResolver.DEFAULT.resolve("${my.key:fallback}");
See [@Value Annotation Basics](./ValueAnnotationBasics.md) for the broader
picture of how
`${...}` ties into declarative configuration injection and Spring Boot interop.
+
+## `#{...}` function-call syntax (9.5.0)
+
+Alongside the variable forms above, `VarResolver.DEFAULT` now ships a built-in
function library
+that's invoked via `#{name(args...)}`:
+
+```java
+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, silver:0.5,
*:0)}"); // → "0"
+VarResolver.DEFAULT.resolve("#{len(${csv}, ,)}"); // →
"3" (delimiter-aware part count)
+```
+
+The catalog ships ~68 built-in functions across 10 categories:
+
+- **String** — `upper`, `lower`, `len`, `substring`, `trim`, `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`, `xor`, `eq`, `ne`, …
+- **Conditional** — `if`, `switch`, `coalesce`, `notEmpty`, `case`, …
+- **Regex** — `matches`, `replace`, `extract`, …
+- **Encoding** — `urlEncode`, `urlDecode`, `base64Encode`, `base64Decode`,
`htmlEscape`, `htmlUnescape`, …
+- **Date/time** — `now`, `today`, `formatDate`, `parseDate`, …
+- **Random/UUID** — `uuid`, `randomInt`, `randomString`, …
+- **JSON navigation** — `jsonPath`, `get`, `keys`, `values`, `size`, …
+
+See <a
href="/site/apidocs/org/apache/juneau/commons/svl/functions/package-summary.html"
target="_blank">org.apache.juneau.commons.svl.functions</a>
+for the per-class function lists with full signatures.
+
+### Type coercion
+
+Function arguments are auto-coerced from their resolved string form to the
function's declared
+parameter type (via `TypedFunction` reflection + `ArgCoercer`). Supported
coercions:
+
+- Primitive numerics (`int`, `long`, `double`) and their boxed equivalents.
+- `boolean` — truthy values are `"true"`, `"yes"`, `"on"`, `"1"`; everything
else is `false`
+ (including `null` and empty string). Boolean tests in `#{if(...)}` and
`#{switch(...)}` follow
+ the same rule.
+- `String[]` — a JSON-array literal (`["a","b","c"]`) or comma-separated
tokens, depending on the
+ function's documented arg style.
+- `String` — passed through verbatim.
+
+### Glob matching in `switch(...)`
+
+`#{switch(value, pat1:result1, pat2:result2, ..., *:default)}` matches each
pattern as a glob:
+`*` matches any character sequence, `?` matches a single character. The legacy
`$SW{...}` Var
+used the same semantics; the function form preserves that exactly.
+
+### `VarFunction` SPI for user-defined functions
+
+User code can publish additional functions through:
+
+1. **Explicit registration** —
`VarResolver.create().functions(MyFunction.class).build()`.
+2. **`ServiceLoader` discovery** — declare your `VarFunction` class in
+ `META-INF/services/org.apache.juneau.commons.svl.VarFunction`. Picked up
automatically by
+ `VarResolver.create().defaultFunctions().build()` and by
`VarResolver.DEFAULT`.
+3. **Per-RestContext** — `@RestInject(name="varResolver")` to install a custom
resolver, then
+ `.functions(...)` on the builder.
+
+Subclass `TypedFunction` to get reflection-derived arity + arg type coercion
for free, or
+implement `VarFunction` directly for full control.
+
+## `VarTemplate` compiled-template API (9.5.0)
+
+`VarResolver.compile(input)` returns a precompiled
+<a href="/site/apidocs/org/apache/juneau/commons/svl/VarTemplate.html"
target="_blank">VarTemplate</a>
+that tokenizes the template once and caches the segment array. Subsequent
`resolve(...)` calls
+skip tokenization and var-registry lookup, walking the cached segments
directly.
+
+```java
+// Compile once at startup (cache on field, in framework metadata, etc.).
+VarTemplate t = VarResolver.DEFAULT.compile("hello ${user.name:guest}");
+
+// Resolve as many times as you want.
+String s = t.resolve(VarResolver.DEFAULT.createSession());
+```
+
+The same `VarResolverSession.resolve(String)` you already use is built on top
of `compile(...)`
+internally — calling `compile(...)` explicitly is an opt-in optimization for
callers that
+resolve the same template repeatedly (framework injection sites, request
routers, etc.).
+
+### `resolveSupplier(...)` for live-reload
+
+`vr.resolveSupplier(input)` returns a `Supplier<String>` that re-evaluates the
template on every
+`.get()` call against a fresh session, so the returned Supplier is safe to
share across threads:
+
+```java
+Supplier<String> live =
VarResolver.DEFAULT.resolveSupplier("${myapp.title:Untitled}");
+// Anywhere across the app:
+String now = live.get();
+```
+
+### Which Supplier do I want?
+
+| You want… | Use | Notes |
+|---|---|---|
+| Re-evaluate on every `.get()`, safe across threads. |
`vr.resolveSupplier(...)` or `tpl.asSupplierWithFreshSessions(vr)` | Opens a
fresh session per call. **Recommended default.** |
+| Re-evaluate on every `.get()`, but I own the session. |
`session.resolveSupplier(...)` or `tpl.asSupplier(session)` | Captures the
existing session — inherits its threadsafety contract. Perf-sensitive callers
only. |
+| Resolve once at compile/build time. | `tpl.resolve(currentSession)` |
One-shot. |
+
+### `VarTemplate.isLiteral()`
+
+For templates that contain no variables (or whose variables fold to literals
via stable-value
+folding — see below), `isLiteral()` returns `true` and `resolve(...)` returns
a pre-computed
+String constant. Callers can skip Supplier wrapping for literal-only inputs.
+
+### Stable-value folding
+
+Four built-in Vars opt in to compile-time folding via the `Var.isStable()` SPI:
+
+- `EnvVariablesVar` (`$E{...}`) — process env is immutable for the JVM
lifetime.
+- `SystemPropertiesVar` (`$S{...}`) — folds at compile time;
`System.setProperty(...)` calls
+ *after* compile do **not** propagate. Documented caveat.
+- `ManifestFileVar` (`$MF{...}`) — classpath manifest entries are immutable.
+- `ArgsVar` (`$A{...}`) — process argv is immutable.
+
+When the compiler sees one of these in a template with a literal body, it
eagerly resolves the
+value and replaces the segment with a `LiteralSegment`. The runtime dispatch
is eliminated.
+
+`ConfigVar`, `PropertyVar`, `DotenvVar`, `EnvFileVar` stay non-stable — their
backing source can
+mutate (config reload, file watch, runtime `Settings.set`), so folding would
produce stale reads.
+
+## `Supplier<String>` field-type semantics
+
+Within `@Value`-driven configuration injection (see [@Value Annotation
+Basics](./ValueAnnotationBasics.md)), the **field type** controls when the SVL
expression is
+re-evaluated:
+
+| Declared field type | When does it resolve? | Threadsafe? |
+|---|---|---|
+| `String` | Once at bean construction. The captured value is held for the
bean's lifetime. | Trivially — it's just a `String`. |
+| `Supplier<String>` | On every `Supplier.get()` call. Each call opens a fresh
`VarResolverSession`. | Yes. |
+
+No `@Value(supplier=true)` flag is required. The field type *is* the opt-in
signal.
+
+```java
+public class JwtValidator {
+
+ // Resolves once. If "jwt.cache.ttl" changes after bean creation, this
stays stale.
+ @Value("${jwt.cache.ttl:PT5M}")
+ Duration ttl;
+
+ // Re-evaluates on every .get(). Pick up live reloads automatically.
+ @Value("${jwt.cache.ttl:PT5M}")
+ Supplier<String> liveTtl;
+}
+```
+
+### Patterns emerging from composition
+
+Combining the `Supplier<String>` field type with the `#{...}` function library
produces
+expressive live-factory patterns:
+
+```java
+// Request-ID factory — fresh UUID per .get().
+@Value("#{uuid()}")
+Supplier<String> requestId;
+
+// Idempotency key for a downstream API — random per call.
+@Value("#{concat(ord-, #{randomString(12)})}")
+Supplier<String> idempotencyKey;
+
+// Jittered retry delay — random 100–500ms each call.
+@Value("PT0.#{randomInt(100, 500)}S")
+Supplier<String> retryDelayIso;
+
+// Live ISO timestamp.
+@Value("#{now()}")
+Supplier<String> nowIso;
+
+// Hot-reload config knob — re-reads from Settings on every .get(),
+// so an external "Settings.set(...)" call surfaces without re-creating the
bean.
+@Value("${myapp.cache.policy:write-through}")
+Supplier<String> cachePolicy;
+```
+
+### Stable-folding interaction
+
+Functions (`#{...}`) are **never folded** — `#{uuid()}` inside a
`Supplier<String>` field always
+produces a fresh value per `.get()`. By contrast, a `${env.HOME}` reference
inside a
+`Supplier<String>` field is folded at compile time because
`EnvVariablesVar.isStable()` returns
+`true` — every `.get()` returns the same captured value (no Supplier overhead
beyond returning the
+literal).
+
+The takeaway: the "fresh per `.get()`" guarantee applies to **functions** and
**non-stable
+`Var`s**. Stable `Var` lookups behave like resolve-once even inside a
`Supplier<String>` field.
+This is intentional — those values can't actually change.
+
+## See also
+
+- [@Value Annotation Basics](./ValueAnnotationBasics.md) — declarative config
injection.
+- [Variable Basics](./VariableBasics.md) — how variables work inside a
`Config`.
+- [SVL Variables](./SvlVariables.md) — REST-context variable list.
+- <a href="/site/apidocs/org/apache/juneau/commons/svl/VarResolver.html"
target="_blank">`org.apache.juneau.commons.svl.VarResolver`</a>
+- <a href="/site/apidocs/org/apache/juneau/commons/svl/VarTemplate.html"
target="_blank">`org.apache.juneau.commons.svl.VarTemplate`</a>
+- <a href="/site/apidocs/org/apache/juneau/commons/svl/VarFunction.html"
target="_blank">`org.apache.juneau.commons.svl.VarFunction`</a>
+- <a
href="/site/apidocs/org/apache/juneau/commons/svl/functions/package-summary.html"
target="_blank">`org.apache.juneau.commons.svl.functions`</a>
diff --git a/pages/topics/02.21.06.ValueAnnotationBasics.md
b/pages/topics/02.21.06.ValueAnnotationBasics.md
index 2c062d88b5..1420e088b8 100644
--- a/pages/topics/02.21.06.ValueAnnotationBasics.md
+++ b/pages/topics/02.21.06.ValueAnnotationBasics.md
@@ -102,6 +102,66 @@ public class JwtValidator {
}
```
+## `Supplier<String>` field type — re-evaluating reads (9.5.0)
+
+Declaring a `@Value` field as `Supplier<String>` instead of `String` makes the
SVL expression
+re-evaluate on every `.get()` call against a fresh `VarResolverSession`. No
annotation flag is
+required — **the field type IS the opt-in signal** (per TODO-103 RD #9
autodetect).
+
+```java
+public class JwtValidator {
+
+ // String field: resolves ONCE at bean construction. Captures whatever
Settings/PropertyVar
+ // returned at injection time and holds that value for the bean's lifetime.
+ @Value("${jwt.cache.ttl:PT5M}")
+ String ttl;
+
+ // Supplier<String> field: re-evaluates on every .get() call against a
fresh session.
+ // Picks up live Settings.set(...) updates, file-watch reloads, Spring env
changes, etc.
+ @Value("${jwt.cache.ttl:PT5M}")
+ Supplier<String> liveTtl;
+}
+```
+
+The returned `Supplier` is **threadsafe** — each `.get()` opens its own
`VarResolverSession`,
+so it can be cached on shared fields or handed across threads. (Internally
this delegates to
+`VarTemplate.asSupplierWithFreshSessions(...)`.)
+
+Literal-only expressions (no variables) take a constant-folding fast path: the
resolved string
+is captured once at compile time and every `.get()` returns the same cached
value with no
+session overhead.
+
+### Composition with `#{...}` functions
+
+Combining the `Supplier<String>` field type with the `#{...}` function catalog
produces concise
+live-factory patterns:
+
+```java
+// Request-ID factory — fresh UUID per request.
+@Value("#{uuid()}")
+Supplier<String> requestId;
+
+// Idempotency key for a downstream API — random per call.
+@Value("#{concat(ord-, #{randomString(12)})}")
+Supplier<String> idempotencyKey;
+
+// Jittered retry delay (ISO 8601 duration with 100–500ms jitter).
+@Value("PT0.#{randomInt(100, 500)}S")
+Supplier<String> retryDelayIso;
+
+// Live ISO timestamp on every call.
+@Value("#{now()}")
+Supplier<String> nowIso;
+
+// Hot-reload config — picks up Settings.set(...) without bean re-creation.
+@Value("${myapp.cache.policy:write-through}")
+Supplier<String> cachePolicy;
+```
+
+See [Simple Variable Language Basics — `Supplier<String>` field-type
+semantics](/docs/topics/SimpleVariableLanguageBasics#suppliersstring-field-type-semantics)
for
+the canonical decision table and stable-folding interaction.
+
## Spring `@Value` compatibility
For consumers migrating from Spring Boot,
`org.springframework.beans.factory.annotation.Value`
@@ -176,10 +236,12 @@ The exception message identifies the offending site to
make the conflict easy to
## See also
-- [Simple Variable Language Basics](./SimpleVariableLanguageBasics.md) — the
full SVL surface.
+- [Simple Variable Language Basics](./SimpleVariableLanguageBasics.md) — the
full SVL surface,
+ the `#{...}` function catalog, and the `VarTemplate` compiled-template API.
- [SVL Variables](./SvlVariables.md) — list of built-in `$X{...}` resolvers.
- [Variable Basics](./VariableBasics.md) — how variables work inside a
`Config`.
- <a href="/site/apidocs/org/apache/juneau/commons/inject/Value.html"
target="_blank">org.apache.juneau.commons.inject.Value</a>
- <a href="/site/apidocs/org/apache/juneau/commons/settings/Settings.html"
target="_blank">org.apache.juneau.commons.settings.Settings</a>
- <a href="/site/apidocs/org/apache/juneau/commons/svl/vars/PropertyVar.html"
target="_blank">org.apache.juneau.commons.svl.vars.PropertyVar</a>
+- <a href="/site/apidocs/org/apache/juneau/commons/svl/VarTemplate.html"
target="_blank">org.apache.juneau.commons.svl.VarTemplate</a>
- <a
href="/site/apidocs/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource.html"
target="_blank">org.apache.juneau.rest.springboot.SpringEnvironmentPropertySource</a>
diff --git a/pages/topics/10.13.SvlVariables.md
b/pages/topics/10.13.SvlVariables.md
index e50b0ebc67..999cda4dd0 100644
--- a/pages/topics/10.13.SvlVariables.md
+++ b/pages/topics/10.13.SvlVariables.md
@@ -52,13 +52,6 @@ The following is the default list of supported variables:
| | <a href="/site/apidocs/org/apache/juneau/commons/svl/vars/DotenvVar.html"
target="_blank">DotenvVar</a> | `$DE{key[,default]}` | yes | yes |
`$DE{MY_KEY,default}` |
| | <a href="/site/apidocs/org/apache/juneau/commons/svl/vars/ArgsVar.html"
target="_blank">ArgsVar</a> | `$A{key[,default]}` | yes | yes | `$A{foo,null}` |
| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/ManifestFileVar.html"
target="_blank">ManifestFileVar</a> | `$MF{key[,default]}` | yes | yes |
`$MF{Main-Class}` |
-| | <a href="/site/apidocs/org/apache/juneau/commons/svl/vars/IfVar.html"
target="_blank">IfVar</a> | `$IF{arg,then[,else]}` | yes | yes |
`$IF{$S{my.boolean.property},foo,bar}` |
-| | <a href="/site/apidocs/org/apache/juneau/commons/svl/vars/SwitchVar.html"
target="_blank">SwitchVar</a> | `$SW{arg,p1:then1[,p2:then2...]}` | yes | yes |
`$SW{$S{os.name},*win*:Windows,*:Something else}` |
-| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/CoalesceVar.html"
target="_blank">CoalesceVar</a> | `$CO{arg1[,arg2...]}` | yes | yes |
`$CO{$S{my.property},$E{my.property},n/a}` |
-| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/PatternMatchVar.html"
target="_blank">PatternMatchVar</a> | `$PM{arg,pattern}` | yes | yes |
`$PM{$S{os.name},*win*}` |
-| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/NotEmptyVar.html"
target="_blank">NotEmptyVar</a> | `$NE{arg}` | yes | yes | `$NE{$S{foo}}` |
-| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/UpperCaseVar.html"
target="_blank">UpperCaseVar</a> | `$UC{arg}` | yes | yes | `$UC{$S{foo}}` |
-| | <a
href="/site/apidocs/org/apache/juneau/commons/svl/vars/LowerCaseVar.html"
target="_blank">LowerCaseVar</a> | `$LC{arg}` | yes | yes | `$LC{$S{foo}}` |
| **juneau-config** | <a
href="/site/apidocs/org/apache/juneau/config/vars/ConfigVar.html"
target="_blank">ConfigVar</a> | `$C{key[,default]}` | yes | yes |
`$C{REST/staticFiles}` |
| **juneau-rest-server** | <a
href="/site/apidocs/org/apache/juneau/rest/vars/FileVar.html"
target="_blank">FileVar</a> | `$F{path[,default]}` | yes | yes |
`$F{resources/MyAsideMessage.html, Oops not found!}` |
| | <a
href="/site/apidocs/org/apache/juneau/rest/vars/ServletInitParamVar.html"
target="_blank">ServletInitParamVar</a> | `$I{name[,default]}` | yes | yes |
`$I{my.param}` |
@@ -75,6 +68,12 @@ The following is the default list of supported variables:
| | <a href="/site/apidocs/org/apache/juneau/rest/vars/UrlEncodeVar.html"
target="_blank">UrlEncodeVar</a> | `$UE{uriPart}` | yes | yes |
`$U{servlet:/foo?bar=$UE{$RA{bar}}}` |
| | <a href="/site/apidocs/org/apache/juneau/rest/widget/Widget.html"
target="_blank">Widget</a> | `$W{name}` | no | yes | `$W{MenuItemWidget}` |
+Transformation/conditional logic that used to be a dedicated `Var` class
(`$UC{}`, `$IF{}`,
+`$SW{}`, `$PM{}`, `$PR{}`, `$PE{}`, `$NE{}`, `$CO{}`, `$LN{}`, `$ST{}`,
`$LC{}`) is now expressed
+via the `#{name(args)}` function-call syntax introduced in 9.5.0. See
+[Simple Variable Language Basics — `#{...}` function-call
syntax](/docs/topics/SimpleVariableLanguageBasics#-function-call-syntax-950)
+and the [9.5.0 release notes](/docs/release-notes/9.5.0) for the migration
table.
+
Custom variables can be defined by supplying a named `varResolver` bean via
<a href="/site/apidocs/org/apache/juneau/rest/annotation/RestInject.html"
target="_blank">@RestInject</a>.