This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new e274dd1f69 fix: stabilize manifest fallback and update TODO roadmap
e274dd1f69 is described below
commit e274dd1f699561eeb27604d7340b4c2a8b42759b
Author: James Bognar <[email protected]>
AuthorDate: Mon May 25 08:27:13 2026 -0400
fix: stabilize manifest fallback and update TODO roadmap
---
.../rest/convention/BasicVersionResource.java | 10 +-
todo/TODO-20-rest-debug-rethink.md | 360 ++++++++++++++++-----
todo/TODO-79-value-annotation-config-bridge.md | 212 ++++++++++++
todo/TODO.md | 53 +--
4 files changed, 521 insertions(+), 114 deletions(-)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
index 77f87bd5fd..23197aad65 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
@@ -360,12 +360,14 @@ public class BasicVersionResource {
try (var in = u.openStream()) {
var m = new Manifest(in);
// Prefer an
Implementation-Title-bearing manifest; otherwise hold the
- // first one found as a
fallback. Walking each candidate is cheap (one
- // open per manifest in the
resources enumeration).
+ // most recent one as a
fallback. ClassLoader.getResources walks the
+ // parent chain FIRST, so
caller-supplied URLs come last in the
+ // enumeration — last-wins
ensures the explicit classloader argument
+ // trumps any unrelated
MANIFEST.MF leaked from a parent (e.g. JDK
+ // module manifests in the boot
layer, Surefire's manifest-jar, etc.).
if
(m.getMainAttributes().getValue("Implementation-Title") != null)
return toMap(m);
- if (fallback == null)
- fallback = m;
+ fallback = m;
}
}
} catch (IOException e) {
diff --git a/todo/TODO-20-rest-debug-rethink.md
b/todo/TODO-20-rest-debug-rethink.md
index 4a68015a6b..a4dda8aac8 100644
--- a/todo/TODO-20-rest-debug-rethink.md
+++ b/todo/TODO-20-rest-debug-rethink.md
@@ -2,26 +2,27 @@
Source: promoted from `TODO.md` on 2026-05-21.
-## Dependency on TODO-35
+## Dependency on FINISHED-35
-Phase 3 of this plan (test-time `@TestBean DebugConfig` overlay) **depends on
TODO-35
-Phase 2** landing first (`@TestBean` annotation + `JuneauBeanStoreExtension` in
-`juneau-junit5`). Phases 1, 2, and 4 can land independently of TODO-35 — only
the
-test-side ergonomics in Phase 3 need the BeanStore overlay machinery.
-
-If TODO-35 slips, Phase 3 can still land using direct
-`MockRestClient.Builder.debugConfig(DebugConfig)` wiring (proposed in Phase 3
below),
-just without the `@TestBean` declarative form.
+Phase 3 of this plan (test-time `@TestBean DebugConfig` overlay) depends on
+**FINISHED-35** (`@TestBean` annotation + `JuneauBeanStoreExtension` in
+`juneau-junit5`) — already landed, so this dep is satisfied at the time of
+plan-update (2026-05-25). All four phases can proceed without external
blockers.
## Goal
Replace the current debugging mechanism — split across `DebugEnablement`,
`CallLogger`'s parallel `normalRules`/`debugRules` lists, five
`@Rest`/`@RestOp`
attributes, and a single `Boolean` request attribute — with a **single bean +
-single annotation** surface that:
-
-1. Configures per-endpoint debug **at compile time** (annotation) and **at
runtime**
- (injected bean / programmatic `RestRequest` knob).
+single typed annotation slot on `@Rest`/`@RestOp`** that:
+
+1. Configures per-endpoint debug **at compile time** via
`@Rest(debug=@Debug(...))`
+ and `@RestOp(debug=@Debug(...))` (with standalone `@Debug` as an escape
hatch
+ for annotation-composition / inherited-`@Rest` scenarios) and **at
runtime** via
+ an injected `DebugConfig` bean and a programmatic `RestRequest.debug()`
knob.
+ Putting the debug config inside the `@Rest`/`@RestOp` annotation makes those
+ annotations a **source-of-truth for the resource/op's capabilities** — a
reader
+ scanning `@Rest(...)` sees every configurable capability in one place.
2. Carries a **pluggable log format** per matched endpoint
(text-with-detail-levels,
JSON, one-line, SLF4J-structured, capture-for-tests, user-supplied).
3. Plays well with the `BeanStore` so test code can swap the whole debug
config (or
@@ -201,7 +202,9 @@ other format. Result: `BasicTestCaptureCallLogger` deletion
costs zero new
machinery, and tests inspecting captured debug output get the same API as tests
inspecting any other log channel. See open question #9 below.
-### 3. `@Debug` annotation replaces five existing attributes
+### 3. `@Debug` annotation — primary placement on `@Rest`/`@RestOp`, secondary
placement standalone
+
+The `@Debug` annotation type definition:
```java
@Retention(RUNTIME)
@@ -225,14 +228,78 @@ public @interface Debug {
}
```
-- Class-level `@Debug(...)` replaces `@Rest(debug)`, `@Rest(debugDefault)`,
- `@Rest(debugEnablement)`, `@Rest(debugOn)`.
-- Method-level `@Debug(...)` replaces `@RestOp(debug)`.
-- The five existing attributes are deprecated (with identical semantics
retained
- internally for one release) and removed in 9.7.
+`@Rest` and `@RestOp` each gain a new typed slot `debug=@Debug(...)`. The slot
+name `debug` is **reused** for the new typed form (the old `String`-typed slot
+is removed in the same release — see §6 below). The old four `@Rest` debug
+attributes (`debug`, `debugDefault`, `debugEnablement`, `debugOn`) collapse
into
+this single typed slot.
+
+**Primary placement — nested on `@Rest`/`@RestOp` (source-of-truth pattern,
preferred):**
+
+```java
+@Rest(
+ path="/widgets",
+ debug=@Debug(value="conditional", format=JsonFormat.class)
+)
+public class WidgetResource extends BasicRestServlet {
+
+ @RestGet(path="/", debug=@Debug("always"))
+ public List<Widget> list() { ... }
+}
+```
+
+Putting debug config inside `@Rest`/`@RestOp` makes those annotations the
+canonical source-of-truth for the resource/op's capabilities — a reader
scanning
+`@Rest(...)` sees every configurable capability in one place rather than having
+to scan for sibling annotations on the same class.
+
+This pattern generalizes beyond debug. Any future configurable capability that
+grows beyond a `String`/`Class` shape should follow the same
nested-typed-annotation
+placement on `@Rest`/`@RestOp` rather than spawn a new standalone sibling
+annotation. See Open Question #10 below for a candidate follow-on TODO that
+audits the `@Rest`/`@RestOp` surface for other capabilities that could benefit.
+
+**Secondary placement — standalone (escape hatch):**
-Compile-time configuration becomes one annotation per place. Runtime
configuration
-becomes one bean (`DebugConfig`) reachable via the BeanStore.
+```java
+@Debug(value="conditional", format=JsonFormat.class)
+public class WidgetResource extends BaseResource { // @Rest is inherited from
BaseResource
+ ...
+}
+```
+
+Standalone `@Debug` on class or method is retained for cases where:
+
+- The target class inherits `@Rest` from a base class and doesn't re-declare
it.
+- An annotation-composition pattern aggregates debug config independently of
`@Rest`.
+- A method enters the op-method scan via a custom non-`@RestOp` annotation and
+ still needs debug config.
+
+In all of these the standalone form composes with whatever `@Rest`/`@RestOp`
+configuration is in scope.
+
+**Precedence when both placements are present on the same target:**
+
+`@RestOp(debug=@Debug(...))` on the method beats standalone `@Debug` on the
+method, which beats `@Rest(debug=@Debug(...))` on the class, which beats
+standalone `@Debug` on the class. Specificity wins.
+
+**Mapping table — old attributes → new typed slot:**
+
+| Old | New |
+| --- | --- |
+| `@Rest(debug="true")` | `@Rest(debug=@Debug("always"))` |
+| `@Rest(debug="false")` | `@Rest(debug=@Debug("never"))` |
+| `@Rest(debug="conditional")` | `@Rest(debug=@Debug("conditional"))` |
+| `@Rest(debugDefault="true")` | `@Rest(debug=@Debug("always"))` (collapsed) |
+| `@Rest(debugEnablement=MyEnablement.class)` |
`@Rest(debug=@Debug(config=MyDebugConfig.class))` |
+| `@Rest(debugOn="MyResource.doX=true")` |
`@Rest(debug=@Debug(on="MyResource.doX=true"))` |
+| `@RestOp(debug="true")` | `@RestOp(debug=@Debug("always"))` |
+| `@RestOp(debug="conditional")` | `@RestOp(debug=@Debug("conditional"))` |
+
+Compile-time configuration becomes one annotation slot (`debug=@Debug(...)`) on
+`@Rest`/`@RestOp` — with optional standalone `@Debug` for escape hatches.
Runtime
+configuration becomes one bean (`DebugConfig`) reachable via the BeanStore.
### 4. Runtime fluent surface on `RestRequest`
@@ -290,23 +357,76 @@ dial-up of `level` / `format` is just a builder call.
`MockRestClient.Builder.debugConfig(DebugConfig)` method — less ergonomic, but
self-contained.
-### 6. Migration
-
-- **9.5.x (current release line)** — land the new `DebugConfig` + `@Debug` +
- `DebugFormat` alongside the existing system. `BasicDebugEnablement` and
- `BasicCallLogger` are reimplemented as thin adapters that read
`@Rest(debug)` /
- `@RestOp(debug)` / `@Rest(debugOn)` into a `DebugConfig` builder internally.
- Existing tests and resources keep working with **zero code changes**.
-- **9.5.x release notes** — entry in
- `juneau-docs/docs/pages/release-notes/9.5.0.md` (or 9.5.1 if that's open)
under
- `juneau-rest-server`: new annotation, new bean, deprecation note for the old
- surface.
-- **9.6** — deprecate `@Rest(debug)`, `@Rest(debugDefault)`,
- `@Rest(debugEnablement)`, `@Rest(debugOn)`, `@RestOp(debug)`,
`DebugEnablement`,
- `BasicDebugEnablement`, `BasicCallLogger`'s `normalRules` / `debugRules`.
- Migration entry in `juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`
- (TODO-17 territory) with Old → New rows.
-- **9.7** — remove deprecated surface.
+### 6. Migration — hard break, no deprecation cycle
+
+**Decision (2026-05-25):** no two-release deprecation cycle. The old debug
+surface is **removed in 9.5** in the same release that lands the new
+`@Debug` / `DebugConfig` / `DebugFormat` surface. Migration is documented but
+not automated.
+
+**Rationale:** the old surface is a five-attribute mess (`debug`,
`debugDefault`,
+`debugEnablement`, `debugOn` on `@Rest`, plus `debug` on `@RestOp`) with a
+transitional 9.5 wart already in it (the `debug` vs `debugDefault` distinction
+landed mid-release). Carrying it as deprecated-but-functional for a 9.6 → 9.7
+window would mean shipping two parallel code paths (the back-compat adapter
+chain reading old attributes into a new `DebugConfig` builder, plus the new
+typed surface) for a year, doubling the surface area for tests, docs, and
+review. The hard break trades one explicit migration step at the 9.5 boundary
+for a cleaner long-term shape.
+
+**What gets removed in 9.5 (this TODO's PR):**
+
+- `@Rest(debug)` (String) — replaced by `@Rest(debug=@Debug(...))`.
+- `@Rest(debugDefault)` (String) — collapsed into `@Rest(debug=@Debug(...))`.
+- `@Rest(debugEnablement)` (Class) — replaced by
+ `@Rest(debug=@Debug(config=...))`.
+- `@Rest(debugOn)` (String) — replaced by `@Rest(debug=@Debug(on="..."))`.
+- `@RestOp(debug)` (String) — replaced by `@RestOp(debug=@Debug(...))`.
+- `DebugEnablement` class + `BasicDebugEnablement` impl — replaced by
+ `DebugConfig` + `BasicDebugConfig`.
+- `CallLogger.Builder.normalRules` / `debugRules` — collapsed into
+ `DebugConfig.rule(...)` builders with per-rule format + level.
+- `BasicCallLogger`'s parallel-rule wiring — replaced by single-rule resolve
+ through `DebugConfig`.
+- `BasicTestCallLogger`, `BasicTestCaptureCallLogger` — replaced by
+ `CapturingFormat` (or by direct `LogRecordCapture` use per OQ #9).
+
+**What gets preserved:**
+
+- `RestRequest.setDebug()` / `setDebug(Boolean)` / `isDebug()` — kept as
+ one-liner shortcuts over the new `req.debug()` fluent surface.
+- `CallLogger` class itself — refactored to delegate to `DebugConfig`, not
+ removed.
+- System-property knobs (`juneau.restLogger.*` / `JUNEAU_RESTLOGGER_*`) — kept;
+ they map onto `DebugConfig` builder defaults at construction time.
+
+**Migration notes (delivered with the PR):**
+
+A new migration-guide section lands in
+`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md` (TODO-17 territory)
+with explicit Old → New rows covering every removed surface. Sections:
+
+1. **Annotation migration** — Old → New table mirroring the mapping table in
+ §3 above, with side-by-side code samples.
+2. **`DebugEnablement` → `DebugConfig` migration** — for the (small) population
+ of users with a custom `DebugEnablement` subclass: pattern for porting
+ `ReflectionMap<Enablement>` lookups onto `DebugConfig.Builder.rule(...)`.
+3. **`CallLogger` rule-list migration** — pattern for porting parallel
+ `normalRules` / `debugRules` setups onto unified `DebugRule` instances that
+ carry both gating predicate and format.
+4. **Test-side migration** — for users with `BasicTestCallLogger` /
+ `BasicTestCaptureCallLogger` subclasses: pattern for replacing them with
+ `CapturingFormat` (or with direct `LogRecordCapture` per OQ #9 once
+ resolved).
+5. **System-property migration** — none required; the `juneau.restLogger.*`
+ knobs continue to work and now feed the new `DebugConfig` builder.
+
+**Release-notes entry** in `juneau-docs/docs/pages/release-notes/9.5.0.md`
+under `### juneau-rest-server` with a `**Breaking change**` callout pointing
+at the migration-guide section.
+
+Future-release impact: 9.6 onwards has no carried-over deprecated debug
+surface to remove — TODO-20 lands clean in one shot.
### 7. Class summary
@@ -318,7 +438,9 @@ self-contained.
| `DebugFormat` (interface) | `org.apache.juneau.rest.debug` |
`juneau-rest-server` | Pluggable formatter. |
| `BasicTextFormat`, `OneLineFormat`, `JsonFormat`, `CapturingFormat` |
`org.apache.juneau.rest.debug.format` | `juneau-rest-server` | Built-in
formats. |
| `Slf4jStructuredFormat` | `org.apache.juneau.rest.debug.format.slf4j` | new
`juneau-rest-server-slf4j` | SLF4J-only sub-module; keeps core SLF4J-free. |
-| `@Debug` | `org.apache.juneau.rest.annotation` | `juneau-rest-server` |
Replaces 5 existing attributes. |
+| `@Debug` | `org.apache.juneau.rest.annotation` | `juneau-rest-server` | Used
both as `@Rest(debug=@Debug(...))` / `@RestOp(debug=@Debug(...))` (primary,
source-of-truth) and standalone on class/method (secondary, escape hatch). |
+| `@Rest.debug` slot type change | `org.apache.juneau.rest.annotation` |
`juneau-rest-server` | `String` → `@Debug`. Hard break — see §6. |
+| `@RestOp.debug` slot type change | `org.apache.juneau.rest.annotation` |
`juneau-rest-server` | `String` → `@Debug`. Hard break — see §6. |
| `RestRequest.debug()` fluent | `org.apache.juneau.rest` |
`juneau-rest-server` | New runtime knob; `setDebug`/`isDebug` retained as
shortcuts. |
## Open questions
@@ -357,11 +479,15 @@ self-contained.
structured form (`@Debug.On({ @Debug.Rule(targets=…, value=…), …})`)?
Recommendation: keep the string form for SVL-resolved system-property use
cases;
add `@Debug.Rule` for the in-source form.
-9. **Integrate `org.apache.juneau.commons.logging` package**. The existing
- `juneau-commons` logging package (`Logger`, `LogRecord`,
`LogRecordListener`,
- `LogRecordCapture`) is the right substrate for the new debug surface to sit
on:
- - **`CapturingFormat`** is a thin wrapper around `LogRecordCapture` (see
- section 4 above) — `try-with-resources` capture, format-string assertions,
+9. **Integrate `org.apache.juneau.commons.logging` package + `CapturingFormat`
vs
+ `LogRecordCapture`**. Two related sub-questions, captured together for
+ readability.
+
+ **(a) Substrate.** The existing `juneau-commons` logging package
+ (`Logger`, `LogRecord`, `LogRecordListener`, `LogRecordCapture`) is the
right
+ substrate for the new debug surface to sit on:
+ - **`CapturingFormat`** is a thin wrapper around `LogRecordCapture` (see §2
+ above) — `try-with-resources` capture, format-string assertions,
`LogRecordListener` plumbing, all already exist.
- **`DebugConfig.logger` field** should accept either a
`java.util.logging.Logger`
(back-compat with today's `CallLogger.Builder.logger(...)`) **or** a
@@ -372,25 +498,70 @@ self-contained.
(`{level}: {msg}`, `{thrown}`, etc.) should be the reference set for any
placeholder vocabulary `BasicTextFormat` / `OneLineFormat` / `JsonFormat`
end up exposing.
+
Recommendation: explicit dependency on `org.apache.juneau.commons.logging`
from
`org.apache.juneau.rest.debug`; no parallel reinvention of listener /
capture /
formatted-message machinery.
-9. **`CapturingFormat` vs `LogRecordCapture`**. Two paths:
+
+ **(b) `CapturingFormat` vs `LogRecordCapture`.** Two paths:
1. Ship a dedicated `CapturingFormat` with its own `AtomicReference<String>`
mechanism (the original draft above).
- 2. **Recommended:** route every `DebugFormat` through
`org.apache.juneau.commons.logging.Logger`
- (already wraps `java.util.logging.Logger` and supports
`addLogRecordListener` +
+ 2. **Recommended:** route every `DebugFormat` through
+ `org.apache.juneau.commons.logging.Logger` (already wraps
+ `java.util.logging.Logger` and supports `addLogRecordListener` +
`captureEvents()`), and have tests use `LogRecordCapture` directly — no
bespoke capturing format needed. The capturing API the user already has
- (`logger.captureEvents()` returning a `LogRecordCapture` `Closeable`) is
the
- idiomatic test surface. The new `DebugConfig.Builder.logger(Logger)`
would
- accept `org.apache.juneau.commons.logging.Logger` (the framework's
- delegating logger) so this composes for free.
+ (`logger.captureEvents()` returning a `LogRecordCapture` `Closeable`) is
+ the idiomatic test surface.
+
Cost of (2): we lose the "format-output-as-string" capture niche (e.g.
capture
the exact JSON the prod system would have emitted, byte-for-byte).
Mitigation:
`LogRecord` already carries the rendered message; tests that need the
rendered
string use `cap.getRecords("{msg}")`. Decision needed before Phase 1.
+10. **Source-of-truth audit follow-on TODO** (new, surfaced 2026-05-25). User
has
+ articulated a broader design principle: *"we want the `@Rest` and `@RestOp`
+ annotations to be a source-of-truth for capabilities."* TODO-20 implements
+ this principle for debug (§3 above). Are there other capabilities on
+ `@Rest`/`@RestOp` today that have grown beyond their original
`String`/`Class`
+ slot shapes and would benefit from the same nested-typed-annotation
upgrade?
+ Quick scan of candidates (not exhaustive):
+ - `@Rest(callLogger=Class)` → potentially
`@Rest(callLogger=@CallLogger(...))`
+ if `@CallLogger` ever grows fields beyond a class reference. Today it's
+ just a class, so no migration needed. **Re-evaluate after TODO-20 lands**
+ — if `DebugConfig` ends up subsuming most `CallLogger` configuration, the
+ `callLogger` slot may shrink further or be eliminated.
+ - `@Rest(swagger=...)` / `@Rest(openApi=...)` (FINISHED-74 territory) —
+ these are already nested annotation types. Already
source-of-truth-shaped.
+ No action.
+ - `@Rest(properties=...)` / `@Rest(beanProperties=...)` — already nested
+ annotation types. Already source-of-truth-shaped. No action.
+ - `@Rest(rolesDeclared)`, `@Rest(roleGuard)` — `String` slots that interact
+ with the AuthN guards landed in FINISHED-69. Worth re-evaluating once
+ TODO-69's `@Auth` surface is in user hands — they could become
+ `@Rest(auth=@Auth(...))`-shaped if the roles-vs-AuthN-guard division
turns
+ out to be ergonomically awkward.
+
+ Recommendation: file a follow-on **TODO-90 — `@Rest`/`@RestOp`
source-of-truth
+ annotation pattern audit** after TODO-20 lands, to do a systematic pass
over
+ every slot on both annotations and identify candidates for the
+ nested-typed-annotation upgrade. Don't scope-creep TODO-20 to do this audit
+ now — it would balloon the PR.
+
+11. **Confirm the hard-break decision boundary**. §6 commits to a hard break in
+ 9.5 (this TODO's PR) — no deprecation cycle. This is consistent with the
+ user's 2026-05-25 direction. Two micro-confirmations worth raising before
+ Phase 2 starts:
+ - **`RestRequest.setDebug()` / `setDebug(Boolean)` / `isDebug()`** — kept
+ per §6. Confirm these stay as one-liner shortcuts and do NOT get removed
+ alongside the annotation surface.
+ - **System-property knobs** (`juneau.restLogger.*` / `JUNEAU_RESTLOGGER_*`)
+ — kept per §6. Confirm these continue to feed `DebugConfig` builder
+ defaults, even though the underlying bean shape changes.
+
+ Recommendation: both kept. The annotation surface is the breaking change;
+ the runtime/operator surface stays stable.
+
## Out of scope
- Rewriting `juneau-rest-client`'s debug surface (`RestClient.Builder.debug()`
/
@@ -421,46 +592,67 @@ self-contained.
- `DebugFormat_Test` — each built-in produces the expected output.
- `CallLogger_DebugConfig_Test` — end-to-end through `MockRestClient`.
-### Phase 2 — `@Debug` annotation + adapter on top of existing annotations
-
-1. Add `@Debug` annotation (class + method scope).
-2. `BasicDebugEnablement` reads `@Debug` first, falls back to `@Rest(debug)` /
- `@RestOp(debug)` / `@Rest(debugOn)` for unchanged user code.
-3. `RestContext.debugEnablement` memoizer simplifies: collapse the `debug` /
- `debugDefault` priority dance to a single `@Debug.value()` read.
-4. Migrate `Rest_Debug_Test` cases over (the file is the canonical 1085-line
- covers-everything test — keep the existing matrix, add a parallel
- `@Debug`-based matrix).
+### Phase 2 — `@Debug` annotation + new typed slots on `@Rest`/`@RestOp` (hard
break)
+
+Per §6, this phase **removes** the old debug surface in the same step that
lands
+the new one — no back-compat adapter, no two-release deprecation cycle.
+
+1. Add `@Debug` annotation (class + method scope per §3).
+2. Add typed `debug=@Debug(...)` slot to `@Rest` and `@RestOp`. Old `String`
+ `debug` / `debugDefault` / `debugEnablement` / `debugOn` attributes on
`@Rest`
+ and `String debug` on `@RestOp` are **deleted** in this step (not
deprecated).
+3. `RestContext.debugEnablement` memoizer is **deleted**. The new
+ `RestContext.debugConfig` memoizer reads `@Rest(debug=@Debug(...))`,
+ `@RestOp(debug=@Debug(...))`, and standalone `@Debug` per the precedence
rules
+ in §3, builds a `DebugConfig`, publishes it into the BeanStore.
+4. `Rest_Debug_Test` (the canonical 1085-line covers-everything test) is
+ **rewritten** to use the new annotation surface — the existing test matrix
+ moves over to `@Rest(debug=@Debug(...))` / `@RestOp(debug=@Debug(...))` /
+ standalone `@Debug` placements. No "parallel matrix" — the old surface no
+ longer exists.
5. Tests:
- - `Debug_Annotation_Test` — full annotation matrix (mirrors today's
- `Rest_Debug_Test`, on the new annotation).
- - `Debug_BackCompat_Test` — confirms old `@Rest(debug)` / `@Rest(debugOn)` /
- `@RestOp(debug)` still produce identical behavior.
+ - `Debug_Annotation_Test` — full annotation matrix on the new surface,
covering
+ all three placement options (nested-on-`@Rest`, nested-on-`@RestOp`,
standalone)
+ and the precedence rules from §3.
+ - `Debug_SourceOfTruth_Test` — confirms that the source-of-truth principle
+ reads correctly: a single `@Rest(debug=@Debug(...))` produces the same
+ resolved `DebugConfig` as the equivalent standalone `@Debug` on the class.
-### Phase 3 — runtime fluent surface + TODO-35 integration
+### Phase 3 — runtime fluent surface + FINISHED-35 integration
-**Depends on TODO-35 Phase 2 for the `@TestBean` form.** Direct
-`MockRestClient.Builder.debugConfig(DebugConfig)` wiring can land
independently.
+**FINISHED-35** is landed, so both forms below ship in this phase.
1. Add `RestRequest.debug()` fluent (returns a `DebugScope` per-request
handle).
Keep `setDebug` / `isDebug` as one-liners on top of it.
2. Add `MockRestClient.Builder.debugConfig(DebugConfig)` for direct test wiring
- (parallel to TODO-35's `.overridingBeanStore(...)`).
+ (parallel to FINISHED-35's `.overridingBeanStore(...)`).
3. Tests:
- `Debug_Runtime_Test` — `req.debug().enable(JsonFormat.class).level(FINE)`
works.
- - `Debug_TestBean_Test` (in `juneau-utest`, after TODO-35 Phase 2 lands) —
uses
- `@TestBean DebugConfig` + `JuneauBeanStoreExtension` to swap formats and
- capture output.
-
-### Phase 4 — deprecate the old surface (9.6)
-
-1. Add `@Deprecated(since="9.6", forRemoval=true)` to `@Rest(debug)`,
- `@Rest(debugDefault)`, `@Rest(debugEnablement)`, `@Rest(debugOn)`,
- `@RestOp(debug)`, `DebugEnablement`, `BasicDebugEnablement`,
- `CallLogger.Builder.normalRules` / `debugRules`, `BasicCallLogger`'s
per-status
- rule wiring.
-2. Release-notes + migration-guide entries
- (`juneau-docs/docs/pages/release-notes/9.5.0.md` and
- `juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`).
-3. No code removal yet — that's the 9.7 cycle.
+ - `Debug_TestBean_Test` — uses `@TestBean DebugConfig` +
+ `JuneauBeanStoreExtension` (from FINISHED-35) to swap formats and capture
+ output without rebuilding `MockRestClient`.
+
+### Phase 4 — migration notes + release notes (juneau-docs)
+
+No code removal here — Phase 2 already removed the old surface. This phase is
+documentation-only.
+
+1. New migration-guide section in
+ `juneau-docs/pages/topics/23.01.V9.5-migration-guide.md` (TODO-17 territory)
+ covering the five Old → New migration tracks per §6:
+ - Annotation migration (Old → New table from §3).
+ - `DebugEnablement` → `DebugConfig` migration (custom-subclass porting
pattern).
+ - `CallLogger` rule-list migration (parallel `normalRules`/`debugRules` →
+ unified `DebugRule`).
+ - Test-side migration (`BasicTestCallLogger` / `BasicTestCaptureCallLogger`
→
+ `CapturingFormat` or `LogRecordCapture` per OQ #9).
+ - System-property migration (none required; auto-mapped).
+2. Release-notes entry in `juneau-docs/docs/pages/release-notes/9.5.0.md` under
+ `### juneau-rest-server` with a `**Breaking change**` callout pointing at
the
+ migration-guide section.
+3. New topic page or sub-section under
+ `juneau-docs/pages/topics/10.20.RestServerDebug.md` (or equivalent slot —
pick
+ the cleanest spot in the existing topics tree) walking through the new
+ `@Debug` placement options, the source-of-truth principle, and worked
+ examples for each `DebugFormat` built-in.
diff --git a/todo/TODO-79-value-annotation-config-bridge.md
b/todo/TODO-79-value-annotation-config-bridge.md
new file mode 100644
index 0000000000..b88c7a0874
--- /dev/null
+++ b/todo/TODO-79-value-annotation-config-bridge.md
@@ -0,0 +1,212 @@
+# TODO-79: Juneau `@Value` annotation + Spring Boot `application.yaml` bridge
+
+Source: TODO.md headline bullet expanded 2026-05-25 after a brainstorming
session that mapped the work against the existing
`org.apache.juneau.commons.settings` + `org.apache.juneau.commons.inject`
infrastructure.
+
+## Goal
+
+Add a Juneau `@Value` annotation that lets beans, fields, setters, and
constructor parameters read configuration values declaratively (analog to
Spring's `@Value`), plus a `${xxx}` shortcut in `VarResolver` and a Spring
`Environment` bridge so that values defined in `application.yaml` /
`application.properties` are reachable through the same
`@Value`/`$P{...}`/`Settings` lookup pipeline as native Juneau `*.cfg` entries.
+
+End-state developer experience:
+
+```java
+// Native Juneau:
+@Rest(path="/orders")
+public class OrdersResource extends RestServlet {
+
+ @Value("${db.url}") // From juneau.cfg, system
props, env, or Spring Environment
+ private String dbUrl;
+
+ @Value("${db.timeout.ms:5000}") // Default of 5000 if unset,
coerced to int
+ private int timeoutMs;
+
+ @Inject
+ public OrdersResource(
+ @Value("${app.name:orders}") String appName,
+ @Value("${app.startInstant}") Instant startInstant // Coerced via
Settings.toType(...)
+ ) { ... }
+}
+
+// Mixed Juneau + Spring `@Value` (FQN-detected, no compile-time Spring dep in
juneau-commons):
+@Rest(path="/billing")
+public class BillingResource extends RestServlet {
+
+ @org.springframework.beans.factory.annotation.Value("${stripe.api.key}")
+ private String stripeKey; // Honored identically to Juneau's @Value
+}
+
+// Free-text resolution via the new shortcut:
+String banner = restRequest.getVarResolver().resolve("Welcome to
${app.name}!");
+```
+
+## Why now
+
+- The `org.apache.juneau.commons.settings.PropertySource` SPI already exists
and is already implemented by `org.apache.juneau.config.ConfigPropertySource`.
The interface seam between `juneau-commons` and `juneau-config` that the
brainstorming session worried about is already in the tree — TODO-79 is mostly
**wiring**, not new infrastructure.
+- `Settings.useServiceLoader()` already calls
`ServiceLoader.load(PropertySourceProvider.class)` at build time (see
[Settings.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java)
lines 281-290), but `juneau-config` ships **no** `PropertySourceProvider` and
**no** `META-INF/services/...PropertySourceProvider` entry, so the wire is
unconnected.
+- The `$P{key,default}` var in
[PropertyVar.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java)
already routes through `Settings.get()` — the unified lookup point. Making
`${xxx}` a shortcut for `$P{xxx}` is a 3-line tokenizer tweak in
`VarResolverSession`, not a new var.
+- `BasicBeanStore` + `BeanInstantiator` already drive `@Bean` / `@Inject` /
`@Named` / `@Autowired` / `@ConditionalOnProperty` field/parameter/setter
resolution via FQN-based annotation matching in
[JsrSupport.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/JsrSupport.java).
Adding `@Value` slots in next to `@Inject` in the same pipeline — and
FQN-matching `org.springframework.beans.factory.annotation.Value` falls out for
free with the same trick that a [...]
+- TODO-69 (AuthN guards) is done, TODO-20 (debug rethink) is next, TODO-78 +
view siblings are queued. TODO-79 sits in Phase G (server-feature track) and
has no hard dependencies on any of those — can land any time after Phase A.
+
+## Research findings (verified 2026-05-25)
+
+Significant facts from reading the current tree that shape the design:
+
+1. **`PropertySource` SPI is in `juneau-commons`**
([PropertySource.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java)).
Returns a `PropertyLookupResult` that distinguishes "missing" from
"present-but-null". Already the neutral interface between `juneau-commons` and
`juneau-config`.
+
+2. **`ConfigPropertySource` exists in `juneau-config`**
([ConfigPropertySource.java](../juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java))
and adapts `Config` to `PropertySource`. It's a 50-line file, no other
adapters needed.
+
+3. **`Settings` already aggregates sources**
([Settings.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java)).
Lookup order is documented at lines 44-53: thread-local override → global
override → sources in reverse insertion order → system props → env. Already
supports type coercion via `StringSetting.asType(Class)` for `Integer`, `Long`,
`Boolean`, `Double`, `Float`, `File`, `Path`, `URI`, `Charset`, plus
reflection-based fallback to `value [...]
+
+4. **`$P{...}` is already the Settings-backed var**
([PropertyVar.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java)).
Subclass of `DefaultingVar` so `$P{key,default}` syntax is already supported.
+
+5. **`JsrSupport` already does FQN-based annotation detection**
([JsrSupport.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/JsrSupport.java)).
Recognizes Juneau, Jakarta, JavaX, and Spring variants of `@Inject` /
`@Autowired` / `@Named` / `@Qualifier` / `@Singleton` / `@PostConstruct` /
`@PreDestroy` without compile-time dependencies on those APIs. Same trick
applies trivially to `@Value`.
+
+6. **`SpringBeanStore` is the natural Spring seam**
([SpringBeanStore.java](../juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/SpringBeanStore.java))
— already captures `ApplicationContext` in its constructor (line 66).
`ApplicationContext.getEnvironment()` gives the `Environment` that the Spring
bridge needs to wrap.
+
+7. **No
`META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider`
exists anywhere in the tree** (`rg` confirmed). `juneau-config`'s
`ConfigPropertySource` is therefore never auto-registered today; consumers must
wire it manually.
+
+8. **JDK ships no usable EL** (verified separately). Nashorn was removed in
JDK 15; `javax.script` is empty without a third-party engine; `StringTemplate`
(JEP 430) was preview in JDK 21-23 and **withdrawn in JDK 24** pending
redesign. Jakarta EL is a separate dep (`org.glassfish:jakarta.el`), SpEL is
`spring-expression`. **For TODO-79 we deliberately ship Tier-1 placeholder
semantics only** (key + default + nesting). `#{...}` expression support is
deferred to a future `juneau-config-el` [...]
+
+9. **`VarResolverSession`'s tokenizer matches `$X{...}`** where `X` is a Var
name (zero or more ASCII chars). The `${...}` shortcut is a single lookahead in
the scanner: when `$` is followed immediately by `{`, emit `P` as the var name.
Backward-compatible — no existing `$X{...}` form changes.
+
+## Resolved decisions (from brainstorming 2026-05-25)
+
+1. **`@Value` lives in `org.apache.juneau.commons.inject`** alongside
`@Inject` / `@Named` / `@Bean`, not in `juneau-microservice`. Symmetric to
`@Inject` (resolves *beans*); `@Value` resolves *strings/primitives*. Spring
places `@Value` in `org.springframework.beans.factory.annotation` alongside
`@Autowired`, not in `spring-boot` — same precedent.
+
+2. **No new interface layer between `juneau-commons` and `juneau-config`.**
`PropertySource` already is the layer. We just need a
`ConfigPropertySourceProvider` + `META-INF/services` entry in `juneau-config`
to flip on the auto-wiring that `Settings.useServiceLoader()` already calls.
+
+3. **Shortcut syntax: `${xxx}` → `$P{xxx}`.** Routes through `Settings` so
Config + env + system properties + Spring `Environment` all participate via the
same lookup. Spring-idiom matches industry mental model; literal
`@Value("${db.url}")` is the same string as Spring's, zero learning curve.
Sidesteps the `MessageFormat`/`Mustache`/SLF4J `{0}` collision that a bare
`{xxx}` shortcut would have.
+
+4. **Expression scope: Tier 1 only** (property-placeholder semantics — key,
default, nesting). No arithmetic, no method calls, no ternary.
`${foo.${env}.url}` works for free because `VarResolver` already resolves inner
vars before passing the key to the outer var. `#{...}` EL deferred to a
separate follow-on TODO if/when demand surfaces.
+
+5. **Spring `@Value` is honored identically to Juneau `@Value`** via
FQN-matching in `JsrSupport`. No compile-time Spring dep in `juneau-commons`.
Both annotations carry the same `${...}` payload, both resolve through the same
`Settings`-backed pipeline.
+
+6. **`@Value` and `@Inject` are mutually exclusive on the same injection
site.** Throw `BeanCreationException` with a clear message if both are present.
`@Value` for strings/primitives, `@Inject` for beans — no overloaded semantics.
+
+7. **`ConfigPropertySourceProvider` is classpath-default-silent.** If
`juneau.cfg` is not on the classpath, return `null` from `create()` (already
filtered out by the SPI's `.filter(Objects::nonNull)` stream). No warning log.
Many deployments will not want a classpath config and shouldn't see noise.
+
+## Architecture
+
+```mermaid
+graph TB
+ subgraph commons [juneau-commons]
+ Value["@Value (NEW)"]
+ JsrSupp[JsrSupport]
+ BeanInst[BeanInstantiator]
+ Settings
+ PVar["$P / ${...} (UPDATED)"]
+ PSP[PropertySourceProvider SPI]
+ end
+ subgraph config [juneau-config]
+ Config
+ CPS[ConfigPropertySource]
+ CPSP["ConfigPropertySourceProvider (NEW)"]
+ services["META-INF/services entry (NEW)"]
+ end
+ subgraph spring [juneau-rest-server-springboot]
+ SpringEnvPS["SpringEnvironmentPropertySource (NEW)"]
+ end
+ BeanInst -- "@Value -> string" --> Settings
+ PVar --> Settings
+ Settings -- "ServiceLoader" --> PSP
+ CPSP --> CPS
+ CPS --> Config
+ services -.declares.-> CPSP
+ SpringEnvPS --> Settings
+```
+
+Lookup precedence inside `Settings` is already "sources in reverse insertion
order, then sys-props, then env". Spring `Environment` is added last by the
Spring auto-config, so it wins over `Config`, which wins over sys-props, which
wins over env.
+
+## Scope
+
+**In scope (v1):**
+
+- **`org.apache.juneau.commons.inject.Value`** annotation. `@Target({FIELD,
METHOD, PARAMETER, CONSTRUCTOR})`, `@Retention(RUNTIME)`, single `String
value()` attribute carrying a `${...}` expression or plain literal.
+- **`JsrSupport` extensions**: `JUNEAU_VALUE`, `SPRING_VALUE` FQN constants +
`isValueAnnotation(AnnotationInfo)` and `valueExpression(AnnotationInfo)`
helpers.
+- **`BeanInstantiator` parameter/field/setter resolution branch** that detects
`@Value` (Juneau or Spring), resolves the expression through a session-scoped
`VarResolver`, and coerces to the target type via `Settings.toType(String,
Class)`.
+- **`${xxx}` shortcut** in `VarResolverSession`'s tokenizer — single lookahead
that lowers `${...}` to `$P{...}`.
+- **`org.apache.juneau.config.ConfigPropertySourceProvider`** +
`META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider`
entry in `juneau-config`. Returns `new
ConfigPropertySource(Config.create().name("juneau.cfg").build())` if a
classpath `juneau.cfg` exists; `null` otherwise.
+- **Microservice + RestContext integration**: when a microservice/RestContext
resolves its own `Config`, push a `ConfigPropertySource` wrapping it onto
`Settings.get().addSource(...)` so per-microservice config shadows the
classpath default.
+- **`org.apache.juneau.rest.springboot.SpringEnvironmentPropertySource`**
wrapping `org.springframework.core.env.Environment`. Auto-registered from the
existing Spring `@Configuration` (or `SpringBeanStore` constructor) so
`application.yaml` / `application.properties` / profile-specific overrides /
CLI args participate.
+- **Tests** in `juneau-utest` covering: `String` / `int` / `Integer` /
`boolean` / `URI` / `Instant` coercion; `${foo}`, `${foo:bar}`,
`${foo.${env}.url}` resolution; `@Value` + Spring `@Value` interchangeability;
per-microservice-config shadowing; Spring `Environment` shadowing of `Config`.
+- **Docs**: release-notes entry under
`juneau-docs/pages/release-notes/9.5.0.md` + new topic page documenting
`@Value` + `${...}` + Spring Boot bridge.
+
+**Explicitly out of scope (v1):**
+
+- **`#{...}` SpEL / Jakarta-EL expressions.** Deferred to a future
`juneau-config-el` module if demand surfaces. The architecture leaves room:
`#{...}` would lower to `$EL{...}` the same way `${...}` lowers to `$P{...}`.
+- **`@ConfigurationProperties`-style prefix binding** (binding `app.db.*` to a
`DbConfig` bean). `@Value` is single-key. Prefix binding is a separate effort
if requested.
+- **Arithmetic / boolean / ternary inside `${...}`.** Pure placeholder
semantics only.
+- **Auto-registering a non-classpath `Config`.**
`ConfigPropertySourceProvider` only looks for `juneau.cfg` on the classpath;
programmatic `Config` instances are wired via the Microservice/RestContext
integration path, not the SPI.
+- **Hot-reload of `@Value`-injected fields when the backing `Config`
changes.** Spring's `@Value` is also single-shot at injection time. `Config`
already has a `ConfigEventListener` for hot-reload-aware code that wants it;
`@Value` users who need that should observe `Config` directly.
+
+## Implementation plan
+
+### Phase 1 — `@Value` annotation + BeanInstantiator wiring (juneau-commons)
+
+- Create
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/Value.java`.
+- Extend
[JsrSupport.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/JsrSupport.java)
with `JUNEAU_VALUE` and `SPRING_VALUE` FQN constants plus
`isValueAnnotation(AnnotationInfo)` and `valueExpression(AnnotationInfo)`
helpers. Mirrors the existing `isNamedAnnotation` / `qualifierValue` pattern.
+- In
[BeanInstantiator.java](../juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanInstantiator.java),
in the parameter/field/setter resolution path that today checks `@Inject` /
`@Autowired` / `@Named`:
+ - If an injection site carries `@Value` (Juneau or Spring), reject it if
`@Inject` is also present (throw `BeanCreationException`).
+ - Otherwise resolve the expression through a session-scoped `VarResolver`
(default vars + `PropertyVar` is enough — `${...}` will already be the shortcut
from Phase 2), then coerce to the target type via `Settings.toType(String,
Class)`.
+ - Wrap coercion failures in a `BeanCreationException` with a clear "could
not coerce '${expr}' resolving to '<value>' to <targetType>" message.
+- Acceptance tests in
`juneau-utest/src/test/java/org/apache/juneau/commons/inject/Value_Test.java`:
+ - `@Value` on constructor parameter, setter, field — all three sites.
+ - String / int / Integer / boolean / URI / Instant target types.
+ - `${missing:default}` path — default used.
+ - `${missing}` with no default — `null` for reference types,
`BeanCreationException` for primitives.
+ - Spring `@Value` (referenced via FQN reflection helper in the test, not a
compile-time dep) honored identically.
+ - `@Value` + `@Inject` on the same site rejected with
`BeanCreationException`.
+
+### Phase 2 — `${xxx}` shortcut in VarResolver (juneau-commons)
+
+- In
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarResolverSession.java`,
the tokenizer scanning for `$X{...}`: add a single special case — when the
scanner sees `$` immediately followed by `{`, treat the segment as if it were
`$P{...}`. No new `Var` class needed.
+- Nested resolution (`${foo.${env}.url}`) falls out for free because
`VarResolver` already recursively resolves inner vars before passing the key to
the outer var.
+- Acceptance tests in
`juneau-utest/src/test/java/org/apache/juneau/commons/svl/DollarBraceShortcut_Test.java`:
+ - `${foo}` resolves identically to `$P{foo}`.
+ - `${foo:bar}` returns `"bar"` when `foo` is unset (uses `DefaultingVar`
semantics).
+ - `${foo.${env}.url}` resolves nested.
+ - Literal `$P{...}`, `$C{...}`, `$E{...}`, `$IF{...}` still work — no
regression. (Re-run the full SVL test suite.)
+ - Escape semantics (`\${literal}`) still pass through unchanged.
+
+### Phase 3 — Auto-register Config as a PropertySource (juneau-config +
juneau-microservice)
+
+- Add
`juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySourceProvider.java`.
+ - Strategy: returns `new
ConfigPropertySource(Config.create().name("juneau.cfg").build())` if a
discoverable `juneau.cfg` exists on the classpath; otherwise returns `null`
(the SPI already filters nulls).
+ - `order()` returns something sensibly low (e.g. `100`) so user-supplied
providers can add themselves "after" by returning a higher number —
`Settings`'s "sources walked in reverse insertion order" means higher-`order()`
providers register last and therefore win.
+- Add
`juneau-core/juneau-config/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider`
containing the FQN of `ConfigPropertySourceProvider`.
+- In
`juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java`'s
build path (the spot where `Config` is resolved into the bean store): also
call `Settings.get().addSource(new ConfigPropertySource(cfg))` so the
per-microservice `Config` shadows the auto-registered classpath default.
+- Same hook in `RestContext.Builder` so non-microservice REST resources that
build their own `Config` get the same treatment.
+- Acceptance tests:
+ - Classpath-only path: drop a `juneau.cfg` with `[s]/k=v` into
`juneau-utest`'s test resources, instantiate a `@Value("${s/k}") String v` bean
**without** a microservice. Expect `"v"`.
+ - Microservice-override path: build a `Microservice` with
`Config.create().memStore().build()` having `[s]/k=micro`; same bean should
resolve to `"micro"` rather than the classpath value.
+
+### Phase 4 — Spring Environment bridge (juneau-rest-server-springboot)
+
+- Add
`juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource.java`.
Wraps `org.springframework.core.env.Environment`. `get(name)` returns
`PropertyLookupResult.present(opt(env.getProperty(name)))` if
`env.containsProperty(name)`, otherwise `PropertyLookupResult.missing()`.
+- Auto-registration: in the existing Spring `@Configuration` in
`juneau-rest-server-springboot` (or via `SpringBeanStore`'s constructor — the
place where `ApplicationContext` is captured), call
`Settings.get().addSource(new
SpringEnvironmentPropertySource(appContext.getEnvironment()))`. Adding it from
Spring guarantees `application.yaml` / `application.properties` /
`--db.url=...` / profiles all participate via Spring's standard resolution
rules.
+- Acceptance test: a `@SpringBootTest` mirroring
[SpringBeanStore_Test.java](../juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringBeanStore_Test.java)
puts `db.url=spring-value` in `application.yaml` (or `@TestPropertySource`),
defines a Juneau `@Rest` resource whose constructor takes `@Value("${db.url}")
String url`, asserts the field is `"spring-value"`. Also verify Spring
`@Value("${db.url}")` (FQN-imported) resolves identically.
+
+### Phase 5 — Docs + release notes
+
+- Release-notes entry under `juneau-docs/pages/release-notes/9.5.0.md`:
+ - **Top-level major change**: "New `@Value` annotation + `${...}` shortcut
+ Spring Boot `application.yaml` bridge".
+ - Module-level entries under `juneau-marshall` (the `${...}` shortcut) and
`juneau-rest-server` / `juneau-rest-server-springboot` (the `@Value` + Spring
bridge).
+- New topic page `juneau-docs/pages/topics/...ValueAnnotationBasics.md`
covering:
+ - `@Value` annotation syntax + supported target types.
+ - `${...}` shortcut + nesting + default-value syntax.
+ - Resolution order (thread-local → global → sources reverse-insertion →
sys-props → env).
+ - Spring Boot bridge — what works automatically, what doesn't.
+- Cross-link from existing `SimpleVariableLanguageBasics` and `VariableBasics`
topic pages.
+
+## Risk notes
+
+- The `${...}` tokenizer change is the only place in the plan that touches a
hot path (every `VarResolver.resolve(...)` call). Mitigation: keep the change
in `VarResolverSession.parse(...)` to a single 3-line lookahead (`$` followed
by `{` → emit `P` as the var name), and run the full existing VarResolver test
suite before any other change in Phase 2.
+- `ConfigPropertySourceProvider`'s classpath-default `juneau.cfg` lookup must
be **silent** when the file is absent (return `null`, no warning log). Many
deployments will not want a classpath config and shouldn't see noise.
+- Spring `Environment.getProperty(...)` can be slow on cold lookups for
unknown keys (it walks every nested `PropertySource`). `Settings` already
memoizes through `StringSetting` so this only bites the first lookup per key
per session.
+- `Settings.get()` is a process-wide singleton. Tests that mutate it via
`addSource(...)` must clean up in `@AfterEach` to avoid cross-test bleed.
Pattern: capture the source returned from `addSource(...)`, hold it in a test
field, remove it in teardown. (May need to add a
`Settings.removeSource(PropertySource)` if one doesn't exist — verify during
Phase 3.)
+- Eager scanning of `@Value` fields on cold-start: `BeanInstantiator` already
scans annotations once per class; adding `@Value` to its set of recognized
annotations doesn't change the asymptotic cost.
+
+## Out of scope / follow-on TODOs
+
+- **`#{...}` SpEL / Jakarta-EL expression support.** Recommended path when it
lands: separate `juneau-config-el` Maven module declaring `jakarta.el` in
`provided` scope, registering a `$EL{...}` var that the `#{...}` shortcut
lowers to. Track as its own TODO post-9.5 if demand surfaces.
+- **`@ConfigurationProperties`-style prefix binding**
(`@ConfigurationProperties(prefix="app.db") DbConfig dbConfig`). `@Value` is
single-key; prefix binding is a separate effort.
+- **Hot-reload-aware `@Value` fields** that automatically re-resolve when the
backing `Config` fires a `ConfigEvent`. Spring's `@Value` is also single-shot
at injection time; users who need hot reload should observe `Config` directly.
diff --git a/todo/TODO.md b/todo/TODO.md
index f192264826..31832f67cb 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -1,8 +1,8 @@
# TODO
-## Execution order (re-evaluated 2026-05-24)
+## Execution order (re-evaluated 2026-05-25)
-Four foundational TODOs (TODO-73, TODO-81, TODO-69) and four mixin packs
(TODO-74–77) have landed; next up per execution order is TODO-78 (JSP view
module). Plans for TODO-71, TODO-82–84, TODO-85–87, and TODO-89 are now fleshed
out (2026-05-24). TODO-20 (rest debug rethink) and TODO-37 (agent instruction
consolidation) remain parked.
+Four foundational TODOs (TODO-73, TODO-81, TODO-69) and four mixin packs
(TODO-74–77) have landed. **TODO-20 (rest debug rethink) was un-parked
2026-05-25** with a hard-break decision + source-of-truth nested-annotation
pattern added to the plan; it is now the next-in-flight item ahead of TODO-78.
Plans for TODO-71, TODO-82–84, TODO-85–87, and TODO-89 are fleshed out
(2026-05-24). TODO-37 (agent instruction consolidation) remains parked.
### Completed foundations + mixin family
@@ -19,49 +19,52 @@ Four foundational TODOs (TODO-73, TODO-81, TODO-69) and
four mixin packs (TODO-7
7. ~~**TODO-69** — AuthN guards + isolated `juneau-rest-server-jwt`
sub-module.~~ ✅ done — see `todo/FINISHED-69-authn-guards-jwt-apikey.md`.
-**Phase B — view infrastructure (hard prereq for Phase C):**
+**Phase B — debug rethink (next in flight):**
-8. **TODO-78** — JSP module (`juneau-rest-server-view-jsp`). Introduces the
generic `View` interface in core `juneau-rest-server` and the
`ResponseProcessor`-based renderer pattern that TODO-82/83/84 build on.
Engine-agnostic POM (Option B) stance baked in. HARD prereq for TODO-82/83/84.
+8. **TODO-20** — Rest debug rethink. Collapses five `@Rest`/`@RestOp` debug
attributes + `DebugEnablement` + parallel `CallLogger` rule lists into a single
`DebugConfig` bean + a typed `@Debug` annotation. **Hard break** (no
deprecation cycle; migration notes in
`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`). **Source-of-truth
pattern** — `@Debug` is nested as `@Rest(debug=@Debug(...))` and
`@RestOp(debug=@Debug(...))` so the `@Rest`/`@RestOp` annotation is the
canonical capab [...]
-**Phase C — view module siblings (parallelizable after TODO-78 lands; no
inter-sibling deps):**
+**Phase C — view infrastructure (hard prereq for Phase D):**
-9. **TODO-82** — Thymeleaf view module. Highest priority of the three (Spring
Boot's default web view; biggest existing-user-base migration path). Plan:
`todo/TODO-82-view-module-thymeleaf.md`.
-10. **TODO-83** — Mustache view module. Smallest surface area, lowest risk.
Plan: `todo/TODO-83-view-module-mustache.md`.
-11. **TODO-84** — FreeMarker view module. Apache-family alignment bonus. Plan:
`todo/TODO-84-view-module-freemarker.md`.
+9. **TODO-78** — JSP module (`juneau-rest-server-view-jsp`). Introduces the
generic `View` interface in core `juneau-rest-server` and the
`ResponseProcessor`-based renderer pattern that TODO-82/83/84 build on.
Engine-agnostic POM (Option B) stance baked in. HARD prereq for TODO-82/83/84.
-**Phase D — application showcase tier:**
+**Phase D — view module siblings (parallelizable after TODO-78 lands; no
inter-sibling deps):**
-12. **TODO-85** — `juneau-microservice-jetty-starter`. Standalone, no hard
deps; can land any time after FINISHED-74/75/76/77 (which it bundles). Plan:
`todo/TODO-85-microservice-jetty-starter.md`.
-13. **TODO-86** — `juneau-petstore-jetty` sample app. Soft deps on TODO-85
(starter fallback to raw `juneau-microservice-jetty`), TODO-69 (auth fallback
to `DenyAllGuard`), TODO-82 (preferred view engine: Thymeleaf, fallback to
TODO-78 JSP). Defines the shared `juneau-petstore-core` module consumed by
TODO-87. Plan: `todo/TODO-86-petstore-jetty-app.md`.
-14. **TODO-87** — `juneau-petstore-springboot` sample app. HARD dep on
`juneau-petstore-core` from TODO-86; should land alongside or immediately after
TODO-86 as a coherent landing. Plan: `todo/TODO-87-petstore-springboot-app.md`.
+10. **TODO-82** — Thymeleaf view module. Highest priority of the three (Spring
Boot's default web view; biggest existing-user-base migration path). Plan:
`todo/TODO-82-view-module-thymeleaf.md`.
+11. **TODO-83** — Mustache view module. Smallest surface area, lowest risk.
Plan: `todo/TODO-83-view-module-mustache.md`.
+12. **TODO-84** — FreeMarker view module. Apache-family alignment bonus. Plan:
`todo/TODO-84-view-module-freemarker.md`.
+
+**Phase E — application showcase tier:**
+
+13. **TODO-85** — `juneau-microservice-jetty-starter`. Standalone, no hard
deps; can land any time after FINISHED-74/75/76/77 (which it bundles). Plan:
`todo/TODO-85-microservice-jetty-starter.md`.
+14. **TODO-86** — `juneau-petstore-jetty` sample app. Soft deps on TODO-85
(starter fallback to raw `juneau-microservice-jetty`), TODO-69 (auth fallback
to `DenyAllGuard`), TODO-82 (preferred view engine: Thymeleaf, fallback to
TODO-78 JSP). Defines the shared `juneau-petstore-core` module consumed by
TODO-87. Plan: `todo/TODO-86-petstore-jetty-app.md`.
+15. **TODO-87** — `juneau-petstore-springboot` sample app. HARD dep on
`juneau-petstore-core` from TODO-86; should land alongside or immediately after
TODO-86 as a coherent landing. Plan: `todo/TODO-87-petstore-springboot-app.md`.
> **Open question for TODO-86/87** (surfaced by planning worker): factor REST
> resource classes into a third sibling `juneau-petstore-rest` module so both
> sample apps consume identical resource classes? Worth resolving before
> either starts implementation.
-**Phase E — quality-of-life follow-ons (parallelizable, any order, anytime
after Phase A):**
+**Phase F — quality-of-life follow-ons (parallelizable, any order, anytime
after Phase A):**
-15. **TODO-89** — `RateLimitGuard.Storage.snapshot()` SPI +
`BasicAdminResource` enrichment. Small (~30 LOC + 2 test files), closes a known
carry-over from FINISHED-77 (`"buckets": []` placeholder). Plan:
`todo/TODO-89-ratelimit-storage-snapshot-spi.md`.
-16. **TODO-71** — Doc-site script + Docusaurus search swap. Mostly already
done (planning worker found `juneau-docs/scripts/build-docs.py` +
`.github/workflows/deploy-docs.yml.disabled` already in place); this TODO is
now a cutover/cleanup + the Algolia → `@easyops-cn/docusaurus-search-local`
swap. Plan: `todo/TODO-71-docs-site-script-search-swap.md`.
-17. **TODO-88** — YAML parser buffer-underflow on large OpenAPI 3.1 documents.
Latent parser bug in `juneau-marshall`; opportunistic. Plan file TBD.
+16. **TODO-89** — `RateLimitGuard.Storage.snapshot()` SPI +
`BasicAdminResource` enrichment. Small (~30 LOC + 2 test files), closes a known
carry-over from FINISHED-77 (`"buckets": []` placeholder). Plan:
`todo/TODO-89-ratelimit-storage-snapshot-spi.md`.
+17. **TODO-71** — Doc-site script + Docusaurus search swap. Mostly already
done (planning worker found `juneau-docs/scripts/build-docs.py` +
`.github/workflows/deploy-docs.yml.disabled` already in place); this TODO is
now a cutover/cleanup + the Algolia → `@easyops-cn/docusaurus-search-local`
swap. Plan: `todo/TODO-71-docs-site-script-search-swap.md`.
+18. **TODO-88** — YAML parser buffer-underflow on large OpenAPI 3.1 documents.
Latent parser bug in `juneau-marshall`; opportunistic. Plan file TBD.
-**Phase F — server-feature track (independent; can interleave anywhere after
Phase A):**
+**Phase G — server-feature track (independent; can interleave anywhere after
Phase A):**
-18. **TODO-67** — Observability (Micrometer + OpenTelemetry). Plan:
`todo/TODO-67-observability-micrometer-otel.md`.
-19. **TODO-68** — Bean Validation (Jakarta Validation 3.x). Plan:
`todo/TODO-68-bean-validation-integration.md`.
-20. **TODO-70** — `CompletableFuture<?>` return-type + virtual-thread
per-request dispatch. Plan:
`todo/TODO-70-async-completablefuture-virtual-threads.md`.
-21. **TODO-79** — Juneau `@Value` annotation + Spring Boot `application.yaml`
bridge for the Config API. Plan file TBD.
+19. **TODO-67** — Observability (Micrometer + OpenTelemetry). Plan:
`todo/TODO-67-observability-micrometer-otel.md`.
+20. **TODO-68** — Bean Validation (Jakarta Validation 3.x). Plan:
`todo/TODO-68-bean-validation-integration.md`.
+21. **TODO-70** — `CompletableFuture<?>` return-type + virtual-thread
per-request dispatch. Plan:
`todo/TODO-70-async-completablefuture-virtual-threads.md`.
+22. **TODO-79** — Juneau `@Value` annotation + Spring Boot `application.yaml`
bridge for the Config API. Plan:
`todo/TODO-79-value-annotation-config-bridge.md`.
### Parked / unscheduled
-- **TODO-20** — Rest debug rethink (parked pending user review).
- **TODO-37** — Agent instruction consolidation (unscoped).
### Natural review seams
-Foundations (TODO-73 + TODO-81 + TODO-69) → mixin family (TODO-74–77) → view
infrastructure + siblings (TODO-78 + TODO-82/83/84) → application showcase
(TODO-85/86/87) → quality-of-life (TODO-89 + TODO-71 + TODO-88) →
server-feature track (TODO-67 + TODO-68 + TODO-70 + TODO-79).
+Foundations (TODO-73 + TODO-81 + TODO-69) → mixin family (TODO-74–77) → debug
rethink (TODO-20) → view infrastructure + siblings (TODO-78 + TODO-82/83/84) →
application showcase (TODO-85/86/87) → quality-of-life (TODO-89 + TODO-71 +
TODO-88) → server-feature track (TODO-67 + TODO-68 + TODO-70 + TODO-79).
## Items
-- [TODO-20] - Rest debug rethink.
+- [TODO-20] Rest debug rethink — collapses `DebugEnablement` + `CallLogger`
rule lists + five `@Rest`/`@RestOp` debug attributes into a single
`DebugConfig` bean + typed `@Debug` annotation. Hard break in 9.5 (no
deprecation cycle) with migration notes. Source-of-truth pattern — `@Debug` is
nested as `@Rest(debug=@Debug(...))` and `@RestOp(debug=@Debug(...))` (primary
placement) with standalone `@Debug` retained as an escape hatch. See
`todo/TODO-20-rest-debug-rethink.md`.
- [TODO-37] - Agent instruction consolidation.
@@ -75,8 +78,6 @@ Foundations (TODO-73 + TODO-81 + TODO-69) → mixin family
(TODO-74–77) → vi
- [TODO-78] JSP servlet support module (`juneau-rest-server-view-jsp`) — new
module shipping `BasicJspResource` mixin + `JspViewRenderer`; isolates Apache
Jasper / `jakarta.servlet.jsp.*` / JSTL deps from core. See
`todo/TODO-78-mixin-jsp-module.md`.
-- [TODO-79] Juneau `@Value` annotation + Spring Boot `application.yaml` bridge
for the Config API — introduce a `@Value("${...}")` annotation on top of
`Config` so beans / fields / setters can read configuration values
declaratively (analog to Spring's `@Value`); add a Spring Boot integration so
values defined in `application.yaml` / `application.properties` are accessible
through the Juneau `Config` API uniformly with native `*.cfg` files. Plan file
TBD.
-
- [TODO-82] Thymeleaf view module (`juneau-rest-server-view-thymeleaf`) —
sibling to TODO-78's JSP module. New Maven module shipping
`BasicThymeleafResource` mixin + `ThymeleafViewRenderer` + `ThymeleafView` impl
of the `View` interface introduced by TODO-78. High priority since Thymeleaf is
Spring Boot's default web view technology; large existing user base for
Spring-Boot-on-Juneau migrations. Same engine-agnostic POM stance as TODO-78
(Option B): bridge module declares Thymeleaf core [...]
- [TODO-83] Mustache view module (`juneau-rest-server-view-mustache`) —
sibling to TODO-78's JSP module. New Maven module shipping
`BasicMustacheResource` mixin + `MustacheViewRenderer` + `MustacheView` impl of
the `View` interface introduced by TODO-78. Logic-less templates; common choice
for content authored by non-Java developers. Same engine-agnostic POM stance as
TODO-78 (Option B): bridge declares the Mustache API in `provided` scope;
example supplies the concrete impl (resolved de [...]