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 8a48a92e10 docs: add 9.5.0 release notes + topic pages for Problem
Details, SSE, and OpenAPI 3.1
8a48a92e10 is described below
commit 8a48a92e106c08af61f28f5efd6264cc067930b8
Author: James Bognar <[email protected]>
AuthorDate: Fri May 22 20:45:09 2026 -0400
docs: add 9.5.0 release notes + topic pages for Problem Details, SSE, and
OpenAPI 3.1
Covers:
- TODO-61: RFC 7807 / 9457 Problem-Details server-side wiring
* New topic page pages/topics/10.20a.RestServerProblemDetails.md
* Sidebar entry
* Release notes section
- TODO-62: Server-side SSE helpers (SseResponseSupport, SseBroadcaster,
SseHeartbeat, arg injection)
* New topic page pages/topics/10.08.RestServerSse.md
* Sidebar entry
* Release notes section
- TODO-63: OpenAPI 3.1 emission + @Rest(apiFormat=...) knob
* Rewrote pages/topics/10.16.02.BasicRestServletSwagger.md to cover both
/api and /openapi
endpoints, the three apiFormat modes (swagger/openapi/both),
components.schemas reuse,
and the ?Swagger / ?OpenApi query mirrors on group resources
* Migration guide rows added in pages/topics/23.01.V9.5-migration-guide.md
* Release notes block describing the spec, resolution precedence, and
follow-up landings
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 143 +++++++++-
pages/topics/10.08.RestServerSse.md | 48 ++++
pages/topics/10.16.02.BasicRestServletSwagger.md | 93 ++++++-
pages/topics/10.20a.RestServerProblemDetails.md | 328 +++++++++++++++++++++++
pages/topics/23.01.V9.5-migration-guide.md | 233 +++++++++++++++-
sidebars.ts | 10 +
6 files changed, 842 insertions(+), 13 deletions(-)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 1dca23691a..bf119275b1 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -6,7 +6,7 @@ title: "Release 9.5.0"
**Date:** TBD
-Juneau 9.5.0 is a minor release with native TOML and YAML support, BSON
(Binary JSON) support for MongoDB-interoperable binary serialization, CBOR
(Concise Binary Object Representation) per RFC 8949 for IoT and constrained
environments, full CSV serializer/parser support, JCS (JSON Canonicalization
Scheme) per RFC 8785 for deterministic hashing and signing, RDF/THRIFT and
RDF/PROTO binary format support, native serialization support for
lazy-evaluated sequence types, large-dataset stream [...]
+Juneau 9.5.0 is a minor release with native OpenAPI 3.1 emission (alongside
Swagger v2, with a per-resource `@Rest(apiFormat=…)` knob), native TOML and
YAML support, BSON (Binary JSON) support for MongoDB-interoperable binary
serialization, CBOR (Concise Binary Object Representation) per RFC 8949 for IoT
and constrained environments, full CSV serializer/parser support, JCS (JSON
Canonicalization Scheme) per RFC 8785 for deterministic hashing and signing,
RDF/THRIFT and RDF/PROTO binary f [...]
### juneau-marshall
@@ -1986,6 +1986,41 @@ String name
### juneau-rest-server
+#### OpenAPI 3.1 Emission + `apiFormat` Knob (TODO-63)
+
+`juneau-rest-server` now ships first-class OpenAPI 3.1 document generation
alongside the existing Swagger v2 path:
+
+- New `org.apache.juneau.rest.openapi` package with `OpenApiProvider` SPI,
`BasicOpenApiProvider`, and `BasicOpenApiProviderSession`. The session
generates OpenAPI 3.1 via a JSON-level transformation of the existing Swagger
2.0 emission, so every existing Swagger-aware annotation surface (`@Schema`,
`@Content`, `@StatusCode`, etc.) round-trips to the OpenAPI document with no
source changes.
+- New `RestRequest.getOpenApi()` accessor and
`RestContext.getOpenApiProvider()` / `RestContext.getOpenApi(Locale)` parallel
the Swagger family.
+- New `RedocUI` swap (in `juneau-bean-openapi-v3`) renders OpenAPI 3.1
documents as a two-column Redoc-style HTML view for `text/html` requests.
+- New SVL variable `$OS{path}` mirrors `$SS{path}` for OpenAPI document
lookups.
+- New `@Rest(openApiProvider=X.class)` attribute lets resources select a
custom provider.
+
+The new `@Rest(apiFormat="…")` attribute selects which spec format the
canonical `/api/*` endpoint serves (and whether the new `/openapi/*` sibling is
mounted):
+
+| Value | `/api/*` | `/openapi/*` |
+|-------|----------|--------------|
+| `"swagger"` (default) | Swagger v2 + Swagger UI for `text/html` | 404 |
+| `"openapi"` | 404 | OpenAPI 3.1 + Redoc for `text/html` |
+| `"both"` | Swagger v2 + Swagger UI | OpenAPI 3.1 + Redoc |
+
+Resolution precedence: `@Rest(apiFormat=…)` (most-derived non-empty value
wins) → system property `juneau.rest.apiFormat` → default `"swagger"`. The
default is `"swagger"` so existing 9.4.x resources keep their pre-9.5.0 surface
unchanged. Surface via `RestContext.getApiFormat()`. Constants live on
`RestServerConstants` (`API_FORMAT_SWAGGER`, `API_FORMAT_OPENAPI`,
`API_FORMAT_BOTH`, `SYSPROP_apiFormat`).
+
+Three additional pieces close out the TODO-63 scope:
+
+- **`components.schemas` reuse.** `BasicOpenApiProviderSession` now runs an
explicit dedup pass after the Swagger-2.0-to-OpenAPI-3.1 transform: any inline
schema that appears two or more times under operation parameter, request-body,
or response-content slots is lifted into `components.schemas` and each
occurrence is rewritten to a `{"$ref":"#/components/schemas/<name>"}` pointer.
Names are derived from the schema's `title` (when present and unique) and fall
back to synthesized `Schema<N [...]
+- **`?Swagger` / `?OpenApi` query mirrors on group resources.**
`BasicGroupOperations` now defines two extra `GET /` overloads gated by
`HasSwaggerQueryParam` / `HasOpenApiQueryParam` matchers, so
`BasicRestServletGroup`, `BasicRestObjectGroup`, and
`BasicSpringRestServletGroup` will serve the Swagger v2 or OpenAPI 3.1 document
inline when a `?Swagger=…` or `?OpenApi=…` query parameter is present. Without
those parameters, `getChildren(RestRequest)` continues to return the standard
`Chil [...]
+- **YAML round-trip coverage.** `OpenApiYamlRoundTrip_Test` exercises
`YamlSerializer.DEFAULT_READABLE` → `YamlParser.DEFAULT` over (a) a hand-built
`OpenApi` bean, (b) the live document produced by a `BasicRestServlet`-based
resource with `apiFormat="openapi"`, and (c) the `/openapi/*` endpoint served
with `Accept: application/yaml`. Each path asserts structural equality across
`openapi`, `info`, `servers`, `paths`, and `components.schemas`.
+
+#### Server-side SSE Helpers (TODO-62)
+
+`juneau-rest-server` now includes an SSE helper layer for streaming endpoints:
+
+- `RestResponse.sse()` returns `SseResponseSupport` for fluent event/comment
writes (`sendEvent(...)`, `comment(...)`, `flush()`, `sendFrom(...)`).
+- `SseBroadcaster` / `SseSubscription` provide in-memory fan-out for
per-connection subscriptions with bounded subscriber queues.
+- `SseHeartbeat` provides optional scheduled `: ping` comments (when a
`ScheduledExecutorService` bean is available).
+- New `RestOpArg` resolvers allow direct `SseBroadcaster` and
`SseSubscription` method-parameter injection in `@RestOp` handlers.
+
#### Dynamic Child Resources (TODO-33)
Parent resources that extend `BasicRestServletGroup` or `BasicRestObjectGroup`
can now mount and unmount children at runtime, in addition to declaring them
statically via `@Rest(children=…)`.
@@ -2195,6 +2230,103 @@ If you previously relied on `@Bean` (inject) overriding
a Spring `@Bean`, you ha
- **`RestResponse.downloadAs(String filename)`** — Sets `Content-Disposition:
attachment` with a quoted `filename` parameter so browsers typically download
the response body (e.g. `res.downloadAs("example.pdf").setContent(bytes)`).
- **`ContentDisposition.attachment(String)`** and
**`HttpHeaders.contentDispositionAttachment(String)`** (juneau-rest-common) —
Build a safe header value (escapes `\` and `"` in the filename; rejects
null/blank names and CR/LF). RFC 5987 `filename*` is not set; use a custom
header value if you need full Unicode filenames.
+#### RFC 7807 / 9457 Problem-Details server-side wiring
+
+Juneau REST resources can now emit `application/problem+json` (RFC 7807 /
9457) responses end-to-end. The wiring complements the `juneau-bean-rfc7807`
module shipped earlier in this release (the typed `Problem` bean) by adding a
response processor, an opt-in annotation flag, a `ProblemException` throw seam,
and a `ProblemMapper` SPI for custom exception-to-`Problem` translation.
+
+##### Opt-in flag — `@Rest(problemDetails)` / per-op fanout
+
+- **`@Rest(problemDetails)`** — `String` attribute, tri-state (`"true"` /
`"false"` / `""` inherits). When `"true"`, uncaught `BasicHttpException`s on
the resource are rendered as `application/problem+json` regardless of the
client `Accept` header (RFC 7807 §3 — the spec encourages this on errors).
+- **`@RestGet(problemDetails)` / `@RestPost(problemDetails)` /
`@RestPut(problemDetails)` / `@RestPatch(problemDetails)` /
`@RestDelete(problemDetails)` / `@RestOptions(problemDetails)` /
`@RestOp(problemDetails)`** — same tri-state attribute on every method-level
annotation. Per-op `"true"` opts an op in on a non-opted-in resource; per-op
`"false"` opts an op out of an opted-in resource; `""` inherits the
resource-level value.
+
+```java
+@Rest(path="/orders", problemDetails="true")
+public class OrderResource {
+
+ @RestGet("/{id}")
+ public Order get(@Path long id) {
+ throw new NotFound("Order {0} not found", id); // → 404
application/problem+json
+ }
+
+ @RestPost
+ public Problem create(Order in) {
+ if (in.balance < in.amount)
+ return Problem.fromStatus(403, "Insufficient credit", "Balance " +
in.balance + " < amount " + in.amount)
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", in.balance);
+ return null;
+ }
+}
+```
+
+##### `ProblemDetailsProcessor` (response processor)
+
+- **`org.apache.juneau.rest.processor.ProblemDetailsProcessor`** — slotted
into the default `responseProcessors` chain after `ThrowableProcessor`. Handles
four content shapes:
+ - A returned `Problem` bean — serialized directly. Honors client `Accept`
strictly.
+ - A returned or thrown `ProblemException` — unwrapped via
`ProblemException.getProblem()`. Honors `Accept` on non-opted-in ops
(back-compat); flips to error-path semantics (ignores `Accept`) on opted-in ops.
+ - A thrown `BasicHttpException` — gated by `isProblemDetails()` (per-op
resolution); on error path ignores `Accept` and adapts via the registered
`ProblemMapper` (if any matches) or `ProblemAdapters.fromException(...)`
otherwise.
+ - Any other thrown `Throwable` — only when an explicit `ProblemMapper`
matches its class hierarchy. Mappers that return `null` are skipped and the
chain continues; if no mapper matches, the processor returns `NEXT`.
+- Status precedence — when `Problem.getStatus()` is non-`null`, the processor
calls `RestResponse.setStatus(int)` with that value. Otherwise it leaves the
existing status alone (`RestSession.run()` normalizes `0` → `200` so per-op
default codes still flow through).
+- `type` field policy — serialized as-is. A `null` `type` is omitted from the
JSON output rather than synthesized as `"type":"about:blank"` (preserves the
absent-vs-explicit distinction).
+
+##### `ProblemException` throw seam
+
+- **`org.apache.juneau.bean.rfc7807.ProblemException`** — `RuntimeException`
carrying a `Problem` payload. Lets handlers throw a custom problem without
manually building a `BasicHttpException`:
+
+```java
+throw new ProblemException(
+ Problem.fromStatus(403, "Insufficient credit", "Balance 30 < cost 50")
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", 30)
+ .set("accounts", List.of("/account/12345", "/account/67890")));
+```
+
+ Lives in `juneau-bean-rfc7807` (the bean module stays free of any
`juneau-rest-server` dep). The processor unwraps it via `instanceof
ProblemException`.
+
+##### `ProblemAdapters` static helper
+
+-
**`org.apache.juneau.bean.rfc7807.adapter.ProblemAdapters#fromException(BasicHttpException)`**
— lives in `juneau-rest-common`. Adapts a `BasicHttpException` (status code,
reason phrase, `getMessage()`) into a `Problem` bean. Used by the processor's
default error-path fallback when no `ProblemMapper` matches. Available for
direct caller use as well.
+
+##### `ProblemMapper` SPI — declarative exception translation
+
+- **`org.apache.juneau.bean.rfc7807.ProblemMapper<T extends Throwable>`** —
pluggable interface lets users map arbitrary throwables into custom `Problem`
shapes without writing a processor. Two methods: `Class<T> getExceptionType()`
(used for hierarchy-aware dispatch) and `Problem map(T exception)`. Returning
`null` defers to the next-most-specific mapper, and ultimately to the built-in
`ProblemAdapters.fromException(...)` fallback for `BasicHttpException`
subclasses.
+- **`org.apache.juneau.bean.rfc7807.ProblemMapperList`** — aggregator bean for
registering more than one mapper on the same resource. The Juneau `@Bean` walk
pairs each `@Bean` factory with its declared return type, so multiple `@Bean
public ProblemMapper foo()` methods on one class collapse onto the single
`ProblemMapper.class` slot; wrap them in a `ProblemMapperList` to keep them all
reachable. The single-mapper case (`@Bean public ProblemMapper<MyException>
mapper()`) keeps working un [...]
+
+```java
+public class InsufficientCreditMapper implements
ProblemMapper<InsufficientCreditException> {
+ public Class<InsufficientCreditException> getExceptionType() { return
InsufficientCreditException.class; }
+ public Problem map(InsufficientCreditException e) {
+ return Problem.fromStatus(403, "Insufficient credit", e.getMessage())
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", e.getBalance())
+ .set("cost", e.getCost());
+ }
+}
+
+@Rest(problemDetails="true")
+public class AccountResource {
+ // Single-mapper case.
+ @Bean public ProblemMapper<InsufficientCreditException> creditMapper() {
return new InsufficientCreditMapper(); }
+
+ // Multi-mapper case.
+ @Bean public ProblemMapperList problemMappers() {
+ return ProblemMapperList.of(new InsufficientCreditMapper(), new
OrderNotFoundMapper());
+ }
+}
+```
+
+##### `ProblemLocalizationStrategy` — future-work seam
+
+- **`org.apache.juneau.bean.rfc7807.ProblemLocalizationStrategy`** —
functional-interface SPI seam for locale-aware translation of `Problem.title` /
`Problem.detail`. The default in-tree implementation is
`ProblemLocalizationStrategy.IDENTITY` (pass-through); the processor consults
the bean store for a registered strategy on every emission and falls back to
`IDENTITY` when none is present, so the seam is invisible at zero cost on the
hot path until a deliberate strategy is contributed.
+- A reference `Messages`-driven (resource-bundle) implementation is
intentionally **out of scope for this release**. The `IDENTITY` default keeps
the call-site contract stable so a future implementation can be dropped in
without changing the processor.
+
+##### `Accept` negotiation policy
+
+- **Error path** (thrown `BasicHttpException` / thrown `ProblemException` /
mapped `Throwable` on opted-in ops): emits `application/problem+json`
regardless of the client `Accept` — the spec encourages this on errors.
+- **Success path** (returned `Problem` / returned `ProblemException`): honors
the client `Accept` strictly. The processor only emits
`application/problem+json` if `Accept` matches it (or `*/*`); otherwise it
passes through to the next processor in the chain unchanged.
+
+See [REST Server — RFC 7807 Problem
Details](/docs/topics/RestServerProblemDetails) for the full topic, including
worked examples for the resource-level opt-in, the per-op fanout, the
`ProblemException` throw path, and `ProblemMapper` registration.
+
### juneau-rest-client
#### REST session option wire helpers
@@ -2791,6 +2923,15 @@ Problem back = JsonParser.DEFAULT.parse(json,
Problem.class);
The matching `Content-Type` constants (`ContentType.APPLICATION_PROBLEM_JSON`
and `ContentType.APPLICATION_PROBLEM_XML`) have shipped in `juneau-rest-common`
since 9.2.x; this module closes the loop by adding the typed wire bean.
+#### Throw + map SPIs
+
+The module also ships the seams used by the new `juneau-rest-server`
problem-details wiring (see [RFC 7807 / 9457 Problem-Details server-side
wiring](#rfc-7807--9457-problem-details-server-side-wiring) above):
+
+- **`ProblemException`** — `RuntimeException` carrying a `Problem`. Lets
handlers throw a custom problem without manually building a
`BasicHttpException`. The bean module owns this type so the
`juneau-rest-server` processor can unwrap it without a reverse dependency.
+- **`ProblemMapper<T extends Throwable>`** — pluggable interface for
translating arbitrary throwables into `Problem` shapes; the server-side
processor discovers it via the resource bean store and dispatches in
most-specific-first order across the exception hierarchy.
+- **`ProblemMapperList`** — aggregator bean for registering more than one
mapper on the same resource (works around the single-slot collapse of multiple
`@Bean public ProblemMapper foo()` factories that share a return type).
+- **`ProblemLocalizationStrategy`** — functional-interface SPI seam for future
locale-aware translation of `Problem.title` / `Problem.detail`. Defaults to
`ProblemLocalizationStrategy.IDENTITY` (pass-through). A reference `Messages`
(resource-bundle) implementation is intentionally deferred to a future release.
+
See [juneau-bean-rfc7807](/docs/topics/JuneauBeanRfc7807) for the full topic.
### juneau-bean-hal (new module)
diff --git a/pages/topics/10.08.RestServerSse.md
b/pages/topics/10.08.RestServerSse.md
new file mode 100644
index 0000000000..7837867e57
--- /dev/null
+++ b/pages/topics/10.08.RestServerSse.md
@@ -0,0 +1,48 @@
+---
+title: "Server-Sent Events"
+slug: RestServerSse
+---
+
+Juneau REST Server now includes SSE response helpers for event-at-a-time
streaming endpoints.
+
+### `RestResponse.sse()` helper
+
+Use `RestResponse.sse()` to configure an SSE response and emit events without
manually juggling writer state:
+
+```java
+@RestGet(path="/stream", serializers=SseSerializer.class)
+public void stream(RestResponse res) throws Exception {
+ try (var sse = res.sse().heartbeat(Duration.ofSeconds(15))) {
+ sse.sendEvent("tick", "one");
+ sse.comment("ping");
+ sse.sendEvent(new SseEvent("tick", "two"));
+ sse.flush();
+ }
+}
+```
+
+The helper sets:
+
+- `Content-Type: text/event-stream`
+- `Cache-Control: no-cache`
+- `X-Content-Type-Options: nosniff`
+- `Content-Encoding: identity`
+
+### Broadcaster pattern
+
+Use `SseBroadcaster` and `SseSubscription` for fan-out from one producer to
many subscribers.
+
+```java
+@RestGet(path="/broadcast", serializers=SseSerializer.class)
+public void broadcast(RestRequest req, RestResponse res, SseBroadcaster
broadcaster) throws Exception {
+ var id = req.getHttpServletRequest().getRequestId();
+ var subscription = broadcaster.subscribe(id);
+ try (var sse = res.sse().heartbeat(Duration.ofSeconds(15))) {
+ sse.sendFrom(subscription);
+ } finally {
+ subscription.close();
+ }
+}
+```
+
+When a subscriber queue is full, the in-memory broadcaster drops the oldest
event so recent events continue to flow.
diff --git a/pages/topics/10.16.02.BasicRestServletSwagger.md
b/pages/topics/10.16.02.BasicRestServletSwagger.md
index f53933440d..5099fe1afb 100644
--- a/pages/topics/10.16.02.BasicRestServletSwagger.md
+++ b/pages/topics/10.16.02.BasicRestServletSwagger.md
@@ -1,9 +1,11 @@
---
-title: "BasicRestServlet/BasicRestObject Swagger"
+title: "BasicRestServlet/BasicRestObject Swagger and OpenAPI 3.1"
slug: BasicRestServletSwagger
---
-Any subclass of <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServlet.html"
target="_blank">BasicRestServlet</a> and <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObject.html"
target="_blank">BasicRestObject</a> gets an auto-generated Swagger UI when
performing an `OPTIONS` request with `Accept:text/html` due to the following
method:
+Any subclass of <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServlet.html"
target="_blank">BasicRestServlet</a> and <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObject.html"
target="_blank">BasicRestObject</a> gets an auto-generated API documentation
page on `GET /api/*` (Swagger v2 by default) and on `GET /openapi/*` (OpenAPI
3.1 when enabled). For an `Accept: text/html` request the document is rendered
through Swagger UI or Redoc respectively; other [...]
+
+The default endpoint is wired by the following two `default` methods on
`BasicRestOperations`:
```java
@RestGet(
@@ -30,16 +32,87 @@ Any subclass of <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestS
SwaggerUI.class
}
)
-@Override /* BasicRestOperations */
-public Swagger getSwagger(RestRequest req) {
+default Swagger getSwagger(RestRequest req) {
+ if (API_FORMAT_OPENAPI.equals(req.getContext().getApiFormat()))
+ throw new NotFound();
return req.getSwagger().orElseThrow(NotFound::new);
}
+
+@RestGet(
+ path="/openapi/*",
+ summary="OpenAPI 3.1 documentation",
+ description="OpenAPI 3.1 documentation for this resource."
+)
+@HtmlDocConfig(rank=10, navlinks={ "back: servlet:/", "json:
servlet:/openapi?Accept=text/json&plainText=true" }, aside="NONE")
+@MarshalledConfig(swaps={ RedocUI.class })
+default OpenApi getOpenApi(RestRequest req) {
+ if (API_FORMAT_SWAGGER.equals(req.getContext().getApiFormat()))
+ throw new NotFound();
+ return req.getOpenApi().orElseThrow(NotFound::new);
+}
```
-The underlying mechanics are simple.
-The <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServlet.html#getSwagger(org.apache.juneau.rest.RestRequest)"
target="_blank">BasicRestServlet.getSwagger(RestRequest)</a> method returns a
<a href="/site/apidocs/org/apache/juneau/bean/swagger/Swagger.html"
target="_blank">Swagger</a> bean consisting of information gathered from
annotations and other sources.
-Then that bean is swapped for a <a
href="/site/apidocs/org/apache/juneau/bean/swagger/ui/SwaggerUI.html"
target="_blank">SwaggerUI</a> bean when
-rendered as HTML.
+The mechanics are the same on both endpoints — the `getSwagger`/`getOpenApi`
methods return the corresponding spec bean (built from annotations and other
sources), and the bean is swapped for a `SwaggerUI` or `RedocUI` rendering bean
when an HTML response is being produced.
+
+## `@Rest(apiFormat=…)` knob
+
+The new `@Rest(apiFormat=…)` attribute selects which spec format the canonical
`/api/*` endpoint serves and whether the new `/openapi/*` sibling is mounted.
The default is `"swagger"` (back-compat).
+
+| Value | `/api/*` | `/openapi/*` |
+|-------|----------|--------------|
+| `"swagger"` (default) | Swagger v2 + Swagger UI for `text/html` | 404 |
+| `"openapi"` | 404 | OpenAPI 3.1 + Redoc for `text/html` |
+| `"both"` | Swagger v2 + Swagger UI | OpenAPI 3.1 + Redoc |
+
+```java
+// Default behavior: Swagger v2 on /api/*; /openapi/* returns 404.
+@Rest
+public class LegacyResource extends BasicRestServlet { ... }
+
+// Modern resource: OpenAPI 3.1 on /openapi/*; /api/* returns 404.
+@Rest(apiFormat="openapi")
+public class ModernResource extends BasicRestServlet { ... }
+
+// Both endpoints active for transitional rollouts.
+@Rest(apiFormat="both")
+public class TransitionResource extends BasicRestServlet { ... }
+```
-Note that to have your resource create Swagger UI, you must either extend from
one of the basic resource classes or
-provide your own <a
href="/site/apidocs/org/apache/juneau/rest/annotation/RestOp.html"
target="_blank">@RestOp</a>-annotated method that returns a <a
href="/site/apidocs/org/apache/juneau/bean/swagger/Swagger.html"
target="_blank">Swagger</a> object and a <a
href="/site/apidocs/org/apache/juneau/bean/swagger/ui/SwaggerUI.html"
target="_blank">SwaggerUI</a> swap.
+Resolution precedence: `@Rest(apiFormat=…)` (most-derived non-empty value
wins) → system property `juneau.rest.apiFormat` → default `"swagger"`. Surface
via `RestContext.getApiFormat()`. Constants live on `RestServerConstants`
(`API_FORMAT_SWAGGER`, `API_FORMAT_OPENAPI`, `API_FORMAT_BOTH`,
`SYSPROP_apiFormat`).
+
+To use either endpoint without extending the basic resource classes, provide
your own `@RestGet`-annotated method that returns a <a
href="/site/apidocs/org/apache/juneau/bean/swagger/Swagger.html"
target="_blank">Swagger</a> or <a
href="/site/apidocs/org/apache/juneau/bean/openapi3/OpenApi.html"
target="_blank">OpenApi</a> bean and the matching `SwaggerUI` / `RedocUI` swap.
+
+## `components.schemas` reuse
+
+The OpenAPI 3.1 emission path collapses duplicated inline schemas into
reusable `components.schemas` entries. Two layers contribute:
+
+- **Bean-class lift via Swagger v2 `definitions`.** `BasicRestOperations`
carries `@JsonSchemaConfig(useBeanDefs="true")`, so when the underlying Swagger
generator encounters a bean class it lifts the schema into
`definitions[<beanClassName>]` and emits
`{"$ref":"#/definitions/<beanClassName>"}` from each operation slot. The
OpenAPI transform rewrites `definitions` to `components.schemas` and
`#/definitions/<name>` refs to `#/components/schemas/<name>` refs.
+- **Inline dedup pass.** After the JSON-level Swagger-2.0 → OpenAPI-3.1
transform, `BasicOpenApiProviderSession` walks every operation parameter,
request-body, and response-content `schema` slot. Inline schemas (those without
`$ref`) that appear two or more times are hoisted into `components.schemas` and
each occurrence is rewritten to a `{"$ref":"#/components/schemas/<name>"}`
pointer. The lifted entry is keyed by the schema's `title` when present and
unique; otherwise a synthesized `Sc [...]
+
+## `?Swagger` / `?OpenApi` query mirrors on group resources
+
+`BasicGroupOperations` defines two `GET /` overloads gated by query parameter
matchers so that group-style resources (`BasicRestServletGroup`,
`BasicRestObjectGroup`, `BasicSpringRestServletGroup`) expose the API documents
inline:
+
+| Query parameter | Returns |
+|-----------------|---------|
+| _(none)_ | `ChildResourceDescriptions` navigation page from
`getChildren(RestRequest)` |
+| `?Swagger=…` (any value) | Swagger v2 document for this group resource
(Swagger UI for `text/html`) |
+| `?OpenApi=…` (any value) | OpenAPI 3.1 document for this group resource
(Redoc UI for `text/html`) |
+
+Each query mirror honors `@Rest(apiFormat=…)`: the `?Swagger` mirror returns
404 when `apiFormat="openapi"`, and the `?OpenApi` mirror returns 404 when
`apiFormat="swagger"` (the default).
+
+```java
+// Default (apiFormat="swagger"):
+// GET / -> navigation page
+// GET /?Swagger -> Swagger v2 doc
+// GET /?OpenApi -> 404
+@Rest(children={MyChild.class})
+public class DefaultGroup extends BasicRestServletGroup {}
+
+// Both formats active:
+// GET / -> navigation page
+// GET /?Swagger -> Swagger v2 doc
+// GET /?OpenApi -> OpenAPI 3.1 doc
+@Rest(apiFormat="both", children={MyChild.class})
+public class DualGroup extends BasicRestServletGroup {}
+```
diff --git a/pages/topics/10.20a.RestServerProblemDetails.md
b/pages/topics/10.20a.RestServerProblemDetails.md
new file mode 100644
index 0000000000..27ea739366
--- /dev/null
+++ b/pages/topics/10.20a.RestServerProblemDetails.md
@@ -0,0 +1,328 @@
+---
+title: "RFC 7807 / 9457 Problem Details"
+slug: RestServerProblemDetails
+---
+
+Juneau REST servers can emit machine-readable [RFC 7807 — Problem Details for
HTTP APIs](https://www.rfc-editor.org/rfc/rfc7807) responses
(`application/problem+json`) end-to-end. The feature couples three pieces:
+
+- The typed `Problem` bean from
[juneau-bean-rfc7807](/docs/topics/JuneauBeanRfc7807) — the wire shape.
+- An opt-in flag (`problemDetails`) on `@Rest` and every `@RestOp`-group
annotation — turns on `application/problem+json` rendering of error responses
for the resource (or single operation).
+- A built-in `ProblemDetailsProcessor` slotted into the default
response-processor chain — recognises returned `Problem`s, thrown
`ProblemException`s, thrown `BasicHttpException`s on opted-in operations, and
any other thrown `Throwable` that a registered `ProblemMapper` chooses to
translate.
+
+RFC 7807 was obsoleted by [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)
in July 2023, but the data model and the `application/problem+json` IANA
registration are unchanged. The feature is RFC 7807 / 9457-compatible by
construction.
+
+## Motivation
+
+Out of the box, an uncaught <a
href="/site/apidocs/org/apache/juneau/http/response/BasicHttpException.html"
target="_blank">BasicHttpException</a> renders as a `text/plain` body
containing the message and a status line. That is fine for browsers and quick
`curl` debugging, but does not carry enough machine-readable detail for API
clients (no extension fields, no typed `type`/`instance` URIs, no consistent
shape for downstream tooling). RFC 7807 standardises that shape — five
canonical me [...]
+
+The Juneau wiring is **opt-in per resource (or per operation)**. Existing
resources keep their `text/plain` error path unchanged; only resources that
declare `@Rest(problemDetails="true")` (or operations that declare
`@RestGet(problemDetails="true")`, etc.) switch to `application/problem+json`
for errors.
+
+## Opt-in semantics
+
+The opt-in flag is a tri-state `String`:
+
+| Value | Effect
|
+|-----------|-----------------------------------------------------------------------------------------------------|
+| `"true"` | The resource (or operation) emits `application/problem+json` for
errors. |
+| `"false"` | The resource (or operation) does not emit problem-details —
falls back to the legacy `text/plain`. |
+| `""` | Inherits from the next-most-derived `@Rest`. For per-op
annotations, inherits from `@Rest(problemDetails)`. |
+
+### Resource-level
+
+```java
+@Rest(path="/orders", problemDetails="true")
+public class OrderResource {
+
+ @RestGet("/{id}")
+ public Order get(@Path long id) {
+ throw new NotFound("Order {0} not found", id); // → 404
application/problem+json
+ }
+}
+```
+
+### Per-operation fanout
+
+The same attribute is available on every method-level annotation: `@RestGet`,
`@RestPost`, `@RestPut`, `@RestPatch`, `@RestDelete`, `@RestOptions`,
`@RestOp`. Per-op `"true"` opts an op in on a non-opted-in resource; per-op
`"false"` opts an op out of an opted-in resource. `""` inherits.
+
+```java
+@Rest(path="/api", problemDetails="false")
+public class ApiResource {
+
+ @RestGet("/legacy") // text/plain on error (inherits "false")
+ public String legacy() { ... }
+
+ @RestGet(path="/orders/{id}", problemDetails="true") //
application/problem+json on error
+ public Order getOrder(@Path long id) {
+ throw new NotFound("Order {0} not found", id);
+ }
+}
+```
+
+### `Accept` negotiation
+
+The processor follows the spec's two-track policy:
+
+- **Error path** — `application/problem+json` is emitted regardless of the
client `Accept` header. RFC 7807 §3 encourages this: the alternative leaves the
client with a 4xx/5xx and a `text/plain` stack trace, which is strictly worse
than a typed error body.
+- **Success path** — the client `Accept` header is honored strictly. If a
`@RestGet` method *returns* a `Problem` (or `ProblemException`), the processor
only emits `application/problem+json` when the client's `Accept` matches that
media type (or `*/*`); otherwise the processor passes through to the next
processor in the chain unchanged.
+
+## Return paths
+
+There are three ways a Juneau handler can produce a `Problem`-shaped response:
+
+### 1. Return a `Problem` directly
+
+The most explicit path — opts in on the call site, regardless of
`@Rest(problemDetails)`:
+
+```java
+@RestPost
+public Problem create(Order in) {
+ if (in.balance < in.amount)
+ return Problem.fromStatus(403, "Insufficient credit", "Balance " +
in.balance + " < amount " + in.amount)
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", in.balance);
+ return null;
+}
+```
+
+This works regardless of the `problemDetails` opt-in flag — returning a
`Problem` is itself the opt-in signal. The flag only controls error-path
(thrown-exception) behavior.
+
+### 2. Throw a `ProblemException`
+
+For the throw idiom, `juneau-bean-rfc7807` ships a small <a
href="/site/apidocs/org/apache/juneau/bean/rfc7807/ProblemException.html"
target="_blank">ProblemException</a> `RuntimeException` that carries a
`Problem`:
+
+```java
+@RestPost
+public Receipt buy(Order in) {
+ if (in.balance < in.amount) {
+ throw new ProblemException(
+ Problem.fromStatus(403, "Insufficient credit", "Balance " +
in.balance + " < amount " + in.amount)
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", in.balance)
+ .set("accounts", List.of("/account/12345", "/account/67890")));
+ }
+ return chargeAccount(in);
+}
+```
+
+On opted-in operations the processor unwraps the `ProblemException`, emits
`application/problem+json`, and ignores the client `Accept` (per the error-path
policy). On non-opted-in operations the processor honors `Accept` strictly —
useful for passing a `Problem`-shaped success response through.
+
+### 3. Throw a `BasicHttpException` (most common)
+
+The simplest path: opt the resource in and keep throwing the regular
`BasicHttpException` subclasses. The processor adapts the exception into a
`Problem` via the default `ProblemAdapters.fromException(...)` helper (status
code, reason phrase, `getMessage()`):
+
+```java
+@Rest(problemDetails="true")
+public class OrderResource {
+
+ @RestGet("/{id}")
+ public Order get(@Path long id) {
+ throw new NotFound("Order {0} not found", id);
+ }
+}
+```
+
+Wire response:
+
+```http
+HTTP/1.1 404 Not Found
+Content-Type: application/problem+json
+
+{
+ "detail": "Order 42 not found",
+ "status": 404,
+ "title": "Not Found"
+}
+```
+
+For finer control over the emitted `Problem` (custom `type` URI, extension
fields, locale-aware `title` / `detail`), register a `ProblemMapper` — see
below.
+
+## The `Problem` shape
+
+See [juneau-bean-rfc7807](/docs/topics/JuneauBeanRfc7807) for the bean
reference. In summary:
+
+| Property | Type | Notes
|
+|------------|-----------|------------------------------------------------------------------------------------|
+| `type` | `URI` | Nullable. Omitted from the wire when unset (no
synthetic `"about:blank"`). |
+| `title` | `String` | Short, human-readable summary of the problem type.
|
+| `status` | `Integer` | OPTIONAL HTTP status code; serialised as a JSON
number when set, omitted when `null`. |
+| `detail` | `String` | Human-readable explanation specific to this
occurrence. |
+| `instance` | `URI` | URI identifying the specific occurrence.
|
+| (extras) | varies | Arbitrary §3.2 extension members; serialise flat at
the top level. |
+
+When `Problem.status` is non-`null`, the processor calls
`RestResponse.setStatus(int)` with that value. Otherwise it leaves the existing
response status alone (`RestSession.run()` normalises `0` → `200`, so per-op
default codes such as `@RestPost`'s default of 200 still flow through
unchanged).
+
+## `ProblemMapper` — declarative exception translation
+
+For richer error shaping, register a <a
href="/site/apidocs/org/apache/juneau/bean/rfc7807/ProblemMapper.html"
target="_blank">ProblemMapper</a> bean on the resource. The SPI is two methods:
+
+```java
+public interface ProblemMapper<T extends Throwable> {
+ Class<T> getExceptionType();
+ Problem map(T exception);
+}
+```
+
+The processor discovers all `ProblemMapper`s reachable from the resource bean
store, picks the **most-specific** mapper (the one whose `getExceptionType()`
sits closest to the thrown class in the hierarchy), and uses its `Problem`
return value. A mapper that returns `null` is treated as "no opinion" — the
processor falls through to the next-most-specific mapper, and ultimately to the
built-in `ProblemAdapters.fromException(...)` fallback for `BasicHttpException`
subclasses.
+
+### Single mapper
+
+```java
+public class InsufficientCreditException extends RuntimeException {
+ private final int balance;
+ private final int cost;
+
+ public InsufficientCreditException(int balance, int cost) {
+ super("Balance " + balance + " < cost " + cost);
+ this.balance = balance;
+ this.cost = cost;
+ }
+
+ public int getBalance() { return balance; }
+ public int getCost() { return cost; }
+}
+
+public class InsufficientCreditMapper implements
ProblemMapper<InsufficientCreditException> {
+ public Class<InsufficientCreditException> getExceptionType() { return
InsufficientCreditException.class; }
+ public Problem map(InsufficientCreditException e) {
+ return Problem.fromStatus(403, "Insufficient credit", e.getMessage())
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .setInstance(URI.create("/account/12345/msgs/abc"))
+ .set("balance", e.getBalance())
+ .set("cost", e.getCost());
+ }
+}
+
+@Rest(problemDetails="true")
+public class AccountResource {
+
+ @Bean public ProblemMapper<InsufficientCreditException> creditMapper() {
+ return new InsufficientCreditMapper();
+ }
+
+ @RestGet("/buy")
+ public Receipt buy() {
+ throw new InsufficientCreditException(30, 50); // translated by
creditMapper()
+ }
+}
+```
+
+Wire response:
+
+```http
+HTTP/1.1 403 Forbidden
+Content-Type: application/problem+json
+
+{
+ "balance": 30,
+ "cost": 50,
+ "detail": "Balance 30 < cost 50",
+ "instance": "/account/12345/msgs/abc",
+ "status": 403,
+ "title": "Insufficient credit",
+ "type": "https://example.com/probs/out-of-credit"
+}
+```
+
+### Multiple mappers — use `ProblemMapperList`
+
+The Juneau `@Bean` bean-store walk pairs each `@Bean` factory method with its
declared return type, so multiple `@Bean public ProblemMapper foo()` factories
on the same resource class collapse onto the single `ProblemMapper.class` slot
(and only one of them survives in the bean store). To register more than one
mapper, wrap them in a <a
href="/site/apidocs/org/apache/juneau/bean/rfc7807/ProblemMapperList.html"
target="_blank">ProblemMapperList</a> — a single bean that carries the ordered
[...]
+
+```java
+@Rest(problemDetails="true")
+public class CheckoutResource {
+
+ @Bean public ProblemMapperList problemMappers() {
+ return ProblemMapperList.of(
+ new InsufficientCreditMapper(),
+ new OrderNotFoundMapper(),
+ new GenericHttpErrorMapper() // broad fallback — runs last by
hierarchy depth
+ );
+ }
+
+ @RestGet("/buy")
+ public Receipt buy() { throw new InsufficientCreditException(30, 50); }
+}
+```
+
+The processor sorts the list by exception-class hierarchy depth and tries each
matching mapper in most-specific-first order, skipping any mapper that returns
`null`. Bean-registration order serves as the tiebreaker for mappers at the
same depth.
+
+### Default fallback
+
+When no `ProblemMapper` matches (or every matching mapper returns `null`) and
the thrown exception is a `BasicHttpException`, the processor calls <a
href="/site/apidocs/org/apache/juneau/bean/rfc7807/adapter/ProblemAdapters.html"
target="_blank">ProblemAdapters.fromException(BasicHttpException)</a> as the
built-in fallback. This is the lowest-friction path — opting a resource in with
`@Rest(problemDetails="true")` and throwing the regular `BasicHttpException`
subclasses (`NotFound`, `Bad [...]
+
+## Localization — future-work seam
+
+The processor consults the resource bean store for a <a
href="/site/apidocs/org/apache/juneau/bean/rfc7807/ProblemLocalizationStrategy.html"
target="_blank">ProblemLocalizationStrategy</a> bean before emitting the
`Problem`, passing the resolved `Problem` and the negotiated request locale
through it. The default behavior is `ProblemLocalizationStrategy.IDENTITY` — a
pass-through that returns the input `Problem` unchanged.
+
+```java
+@FunctionalInterface
+public interface ProblemLocalizationStrategy {
+ Problem localize(Problem problem, Locale locale);
+}
+```
+
+This is a deliberate **future-work seam**, not a finished feature. The
reference `Messages`-driven (resource-bundle) translation pass is intentionally
out of scope for this release; the `IDENTITY` default keeps the call-site
contract stable so a future implementation can be dropped in without changing
the processor.
+
+Users with custom localization needs can already register a strategy bean
today:
+
+```java
+@Bean public ProblemLocalizationStrategy localize() {
+ return (problem, locale) -> {
+ var bundle = ResourceBundle.getBundle("problems", locale == null ?
Locale.ROOT : locale);
+ var typeKey = String.valueOf(problem.getType());
+ if (bundle.containsKey(typeKey + ".title"))
+ problem.setTitle(bundle.getString(typeKey + ".title"));
+ if (bundle.containsKey(typeKey + ".detail"))
+ problem.setDetail(bundle.getString(typeKey + ".detail"));
+ return problem;
+ };
+}
+```
+
+A `null` strategy return is treated as "no opinion" and the pre-localization
`Problem` is used instead.
+
+## Worked example — end-to-end
+
+```java
+@Rest(path="/orders", problemDetails="true")
+public class OrderResource {
+
+ @Bean public ProblemMapperList problemMappers() {
+ return ProblemMapperList.of(new InsufficientCreditMapper());
+ }
+
+ @RestGet("/{id}")
+ public Order get(@Path long id) {
+ // Throws NotFound → adapted via the default ProblemAdapters fallback.
+ return Optional.ofNullable(repo.find(id))
+ .orElseThrow(() -> new NotFound("Order {0} not found", id));
+ }
+
+ @RestPost
+ public Receipt create(Order in) {
+ // Throws InsufficientCreditException → translated by the registered
ProblemMapper.
+ return checkout.buy(in);
+ }
+
+ @RestDelete(path="/{id}", problemDetails="false")
+ public void cancel(@Path long id) {
+ // Per-op opt-out: cancellation errors fall back to legacy text/plain.
+ repo.delete(id);
+ }
+}
+```
+
+The two `GET` and `POST` operations emit `application/problem+json` on error;
the `DELETE` operation opts out per-op and falls back to `text/plain`.
+
+## See also
+
+- [juneau-bean-rfc7807](/docs/topics/JuneauBeanRfc7807) — the `Problem` bean
reference.
+- [Java Method Throwable Types](/docs/topics/JavaMethodThrowableTypes) — what
exceptions Juneau handlers can throw and how they map to HTTP statuses.
+- [HTTP Status Codes](/docs/topics/HttpStatusCodes) — the framework defaults
for non-OK statuses.
+- [Response Processors](/docs/topics/ResponseProcessors) — the processor-chain
mechanism `ProblemDetailsProcessor` slots into.
+
+## Resources
+
+- [RFC 7807 — Problem Details for HTTP
APIs](https://www.rfc-editor.org/rfc/rfc7807)
+- [RFC 9457 — Problem Details for HTTP APIs (obsoletes
7807)](https://www.rfc-editor.org/rfc/rfc9457)
+- [IANA `application/problem+json`
registration](https://www.iana.org/assignments/media-types/application/problem+json)
diff --git a/pages/topics/23.01.V9.5-migration-guide.md
b/pages/topics/23.01.V9.5-migration-guide.md
index 0ca4fe9ecb..1eae1d6893 100644
--- a/pages/topics/23.01.V9.5-migration-guide.md
+++ b/pages/topics/23.01.V9.5-migration-guide.md
@@ -120,6 +120,24 @@ public class MyType {
}
```
+### Removed `@MarshalledProp(properties)` Attribute
+
+The `@MarshalledProp(properties=...)` attribute has been removed without a
deprecation shim. The attribute was used to limit which child properties of a
nested bean / map are rendered by serializers.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@MarshalledProp(properties="f1") public MyChildClass x1` — only `f1`
rendered on the nested child | Drop the attribute (nested beans render in
full); for per-property filtering apply `@Marshalled(properties=...)` at the
**child class** level, or configure include / exclude lists on the
`MarshallingContext.Builder` | Hard removal. The marshalling-context-level
filtering pre-dated `@MarshalledProp(properties)` and remains fully supported. |
+| `BeanPropertyMeta.applyChildPropertiesFilter(...)` /
`BeanPropertyMeta.getProperties()` (override-list accessor) and the underlying
`properties` field/builder setter | Removed | Internal types — no replacement. |
+| `org.apache.juneau.BeanMetaFiltered` | Removed | Sole purpose was wrapping a
`BeanMeta` with a filtered property list; no replacement needed. |
+
+### `@Beanp("*")` on Non-Map Fields
+
+On a field whose type is **not** a `Map`, `@Beanp("*")` (or
`@Beanp(name="*")`) no longer attempts to register a dyna property. The
property name is now taken from the field via the configured `PropertyNamer`;
other `@Beanp` attributes still apply. `Map` fields keep the dyna-property `*`
behavior as before.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@Beanp("*") public String myField;` registered a no-op dyna property keyed
on `*` | Field is named via the configured `PropertyNamer` (typically
`myField`); `*` is treated as if absent | Behavioral. Affects any non-`Map`
field that was annotated with `@Beanp("*")`. Replace with `@Beanp` (no name) to
keep the namer-derived name, or `@Beanp(name="myField")` to pin it explicitly. |
+
## Bean-Modeling Layer Split
The bean-modeling layer has been split out of `juneau-marshall` into
`juneau-commons` (Phase 5 of the bean-layer split). The seven core bean-runtime
types and the `@Name` annotation have moved into
`org.apache.juneau.commons.bean`, and a new set of bean-modeling annotations
and SPI types has been added there. `juneau-commons` now compiles standalone
with no dependency on `juneau-marshall`.
@@ -450,5 +468,216 @@ Closes out the format-control extension work (TODO-4 /
TODO-50 / TODO-52 / TODO-
| `BinaryFormat.BASE64_URL.parse("mQ")` (or any non-3-aligned URL-safe payload
without `-` / `_` chars) threw `IllegalArgumentException: Invalid BASE64 string
length` | Decodes correctly via `Base64.getUrlDecoder()` (which accepts missing
padding) | **Bug fix, not a breaking change.** `BinaryFormat.parse` now honors
the `BASE64_URL` constant directly before falling through to the
format-agnostic wire-shape sniff. No source change required. |
| `JsonParser.DEFAULT.parse("3.14", Object.class)` returned `java.lang.Float`
*(or any JSON-family parser auto-classifying a bare decimal)* | Returns
`java.lang.Double` | **Behavioral change (Bug #5 closure)** — see release notes
for the full rationale. The shared classifier `StringUtils.parseNumber` no
longer auto-compacts to `Float` when the lossless `Float`/`Double` `toString()`
representations happen to match. Affects all JSON-family parsers (JSON / JSON5
/ JSONL / XML / HTML / UON / [...]
-<!-- Additional rows will be populated as 9.5 breaking changes land. See
todo/TODO-17 for the
-ongoing 9.5.0 audit. -->
+## Format-Control: Duration / Period Defaults (TODO-4)
+
+New `org.apache.juneau.DurationFormat` and `org.apache.juneau.PeriodFormat`
enums plus `MarshallingContext.Builder.durationFormat(...)` /
`periodFormat(...)` settings, mirrored on the three Marshalled annotations
(`@Marshalled` / `@MarshalledProp` / `@MarshalledConfig`). The default
`Duration` wire shape changed.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| Default `Duration` serialization → ISO 8601 hours/minutes form
(`Duration.ofHours(48)` → `'PT48H'`) | **Default is now
`DurationFormat.ISO_8601_WITH_DAYS`** (`Duration.ofHours(48)` → `'P2D'`) |
**Behavioral default change.** To restore the pre-9.5 wire shape, configure
`MarshallingContext.Builder.durationFormat(DurationFormat.ISO_8601)` (or
`@MarshalledProp(durationFormat=DurationFormat.ISO_8601)` per-property). The
`DurationFormat` constants are `ISO_8601`, `ISO_8601_WITH_DAYS`, `NANO [...]
+| n/a | New `MarshallingContext.Builder.periodFormat(PeriodFormat)` (default
`ISO_8601`; also `DAYS`) | Additive. Mirrored on the three Marshalled
annotations. |
+| Binary serializers (BSON / CBOR / MsgPack) wrote `Duration` / `Period` as
strings | Emit native numeric wire types when `durationFormat` / `periodFormat`
resolves to a numeric constant (`NANOS` / `MILLIS` / `SECONDS`) |
Source-compatible; wire shape narrows when configured to numeric. |
+
+## Format-Control: Calendar / Date / Temporal / TimeZone / Locale Swap
Deletions (TODO-51)
+
+The legacy `Temporal*Swap` inner-class family and the named root-level
temporal / locale / timezone swaps have been removed **without deprecation
shims**. Configure formats via `MarshallingContext` setters or the new
annotations instead. New enums `CalendarFormat`, `DateFormat`,
`TemporalFormat`, `TimeZoneFormat`, `LocaleFormat` live under
`org.apache.juneau`.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.swaps.TemporalCalendarSwap` and all 17 inner-class
variants (`IsoOffsetDateTime`, `IsoInstant`, `IsoLocalDateTime`,
`IsoZonedDateTime`, etc.) |
`@MarshalledProp(calendarFormat=CalendarFormat.ISO_XXX)` /
`@Marshalled(calendarFormat=...)` /
`MarshallingContext.Builder.calendarFormat(CalendarFormat.ISO_XXX)` | Hard
removal — no `@Deprecated` shim. Replace
`@Swap(TemporalCalendarSwap.IsoYyy.class)` with
`@MarshalledProp(calendarFormat=CalendarFormat.ISO_YYY)`. For builde [...]
+| `org.apache.juneau.swaps.TemporalDateSwap` and all 17 inner-class variants |
`@MarshalledProp(dateFormat=DateFormat.ISO_XXX)` /
`@Marshalled(dateFormat=...)` /
`MarshallingContext.Builder.dateFormat(DateFormat.ISO_XXX)` | Hard removal.
Same migration shape. `DateFormat` constants: ISO variants,
`RFC_1123_DATE_TIME`, `MILLIS` (default `ISO_LOCAL_DATE_TIME`). |
+| `org.apache.juneau.swaps.TemporalSwap` and all 18 inner-class variants |
`@MarshalledProp(temporalFormat=TemporalFormat.ISO_XXX)` /
`@Marshalled(temporalFormat=...)` /
`MarshallingContext.Builder.temporalFormat(TemporalFormat.ISO_XXX)` | Hard
removal. `TemporalFormat` constants: `DEFAULT` (per-subtype default), ISO
variants, `RFC_1123_DATE_TIME`, `ISO_YEAR`, `ISO_YEAR_MONTH`, `MILLIS`. |
+| `org.apache.juneau.swaps.XMLGregorianCalendarSwap` | Built-in —
`XMLGregorianCalendar` properties are always XML-lexical regardless of any
configured `CalendarFormat`. For `Calendar` / `GregorianCalendar` use
`CalendarFormat.XML_FORMAT` as the opt-in. | The always-XML behavior moved into
the post-processor for `XMLGregorianCalendar`; no annotation needed. |
+| `swaps(TimeZoneSwap.class)` / `swaps(ZoneIdSwap.class)` on builders, used to
control root-level `TimeZone` / `ZoneId` wire shape |
`MarshallingContext.Builder.timeZoneFormat(TimeZoneFormat)` (default `ID`; also
`OFFSET`, `NAME_LONG`, `NAME_SHORT`) shared by both `TimeZone` and `ZoneId` |
The `TimeZoneSwap` / `ZoneIdSwap` types still exist but are now thin delegators
that read the resolved `TimeZoneFormat` from the active session. |
+| `swaps(LocaleSwap.class)` for root-level `Locale` wire shape |
`MarshallingContext.Builder.localeFormat(LocaleFormat)` (default `BCP_47`; also
`UNDERSCORE`) | `LocaleSwap` is now a thin delegator. |
+
+**Precedence:** `@MarshalledProp` > `@Marshalled` > `MarshallingContext`
setting > enum default.
+
+## Format-Control: Binary + Enum Swap and Builder Deletions (TODO-52)
+
+The legacy `ByteArraySwap` family, the `OutputStreamSerializer.Builder` /
`InputStreamParser.Builder` `binaryFormat(...)` setters, and the
`useEnumNames(boolean)` setter have been removed **without deprecation shims**.
Configure these via `MarshallingContext.Builder` or the new annotations
instead. New enum `org.apache.juneau.EnumFormat` plus extended
`org.apache.juneau.BinaryFormat`.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.swaps.ByteArraySwap` and the `Base64` / `Hex` /
`SpacedHex` inner-class variants |
`@MarshalledProp(binaryFormat=BinaryFormat.BASE64 / HEX / SPACED_HEX)` /
`@Marshalled(binaryFormat=...)` /
`MarshallingContext.Builder.binaryFormat(...)` | Hard removal. Replace
`@Swap(ByteArraySwap.Base64.class)` with
`@MarshalledProp(binaryFormat=BinaryFormat.BASE64)`. New `BinaryFormat.NOT_SET`
(default, falls through to surrounding session's native shape) and
`BinaryFormat.BASE64_U [...]
+| `OutputStreamSerializer.Builder.binaryFormat(BinaryFormat)` and
`InputStreamParser.Builder.binaryFormat(BinaryFormat)` |
`MarshallingContext.Builder.binaryFormat(BinaryFormat)` (inherited by every
serializer / parser builder via `MarshallingContextable.Builder`) | The setting
now drives both binary and textual sessions. The inherited setter has the same
signature, so most builder chains compile unchanged. |
+| `BeanContext.Builder.useEnumNames(boolean)` and the `useEnumNames` field on
`MarshallingContext` | `MarshallingContext.Builder.enumFormat(EnumFormat.NAME)`
| Hard removal. `useEnumNames()` / `useEnumNames(true)` →
`enumFormat(EnumFormat.NAME)`. Other `EnumFormat` constants: `TO_STRING`
(default), `LOWER_HYPHEN`, `UPPER_HYPHEN`, `LOWER_UNDERSCORE`, `LOWER`,
`UPPER`, `ORDINAL`, `NOT_SET`. |
+| `@Bean(useEnumNames=true)` | `@Marshalled(enumFormat=EnumFormat.NAME)` |
Annotation-attribute rename. |
+| `org.apache.juneau.csv.ByteArrayFormat` |
`org.apache.juneau.csv.CsvByteArrayCellFormat` | Rename to disambiguate from
the new top-level `BinaryFormat`. |
+| `OutputStreamSerializerSession#serializeToString(Object)` honored the
configured `binaryFormat` for debug output | Always emits `BinaryFormat.HEX` |
Source-compatible; behavioral. Provides a stable copy-pasteable hex dump for
debug / display irrespective of the surrounding context's wire-format setting. |
+
+## Format-Control: `ClassSwap` Deletion + Boolean / Float / Currency / Class
Settings (TODO-54)
+
+`ClassSwap` is deleted; bean-property `Class<?>` serialization is driven by
`@MarshalledProp(classFormat=...)` (with `ClassFormat.FQCN` as the default).
New paired enums `BooleanFormat`, `FloatFormat`, `CurrencyFormat`,
`ClassFormat` plus `MarshallingContext.Builder` setters and matching
`@Marshalled(...)` / `@MarshalledProp(...)` / `@MarshalledConfig(...)`
attributes.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.swaps.ClassSwap` |
`@MarshalledProp(classFormat=ClassFormat.FQCN)` /
`@Marshalled(classFormat=...)` /
`MarshallingContext.Builder.classFormat(ClassFormat)` for bean properties; for
top-level `Class<?>` values the new `org.apache.juneau.swaps.ClassFormatSwap`
is registered in `DefaultSwaps` and reads the resolved `ClassFormat` at swap
time | Hard deletion. Default `ClassFormat.FQCN` (≈ `Class.getCanonicalName()`)
is functionally compatible with the old `ClassSwap.getN [...]
+| `MarshallingContext` had no top-level `booleanFormat` / `floatFormat` /
`currencyFormat` / `classFormat` setting | New
`MarshallingContext.Builder.booleanFormat(BooleanFormat)` /
`floatFormat(FloatFormat)` / `currencyFormat(CurrencyFormat)` /
`classFormat(ClassFormat)` settings | Additive. **Defaults:**
`BooleanFormat.TRUE_FALSE`, `FloatFormat.NaN_AS_NULL` (boxed `Float` / `Double`
only — primitive `float` / `double` keep the legacy null-to-zero contract),
`CurrencyFormat.ISO_CODE`, `C [...]
+| `Currency` bean properties at the default `CurrencyFormat` were unrecognized
(no default swap) | `Currency` round-trips via `DefaultSwaps` registration at
`CurrencyFormat.ISO_CODE` / `NOT_SET` | Bug #6 closure; source-compatible. |
+
+## JSON Strict-Mode Separation (`JsonParser` / `JsonSerializer` vs
`Json5Parser` / `Json5Serializer`)
+
+`JsonSerializer` / `JsonParser` now strictly enforce RFC 8259 (double-quoted
strings only). Single-quoted JSON, comments, trailing commas, and unquoted keys
are rejected by `JsonParser` and no longer produced by `JsonSerializer`. JSON5
input / output must go through `Json5Parser` / `Json5Serializer`.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `JsonSerializer.create().json5().build()` — JSON5 output via the strict
serializer | Use `Json5Serializer.create()....build()` directly | Hard removal
of `JsonSerializer.json5()`. No deprecation shim. |
+| `JsonParser.Strict` static instance / `Strict` inner subclass | Use
`JsonParser.DEFAULT` (now strict) for JSON; `Json5Parser.DEFAULT` for JSON5 |
Hard removal. No deprecation shim. |
+| `JsonParser.DEFAULT.parse("{key:'val'}", ...)` accepted JSON5 (unquoted
keys, single quotes) | Throws `ParseException`. Use `Json5Parser.DEFAULT` for
JSON5 input. | **Hard behavioral break.** Callers passing JSON5 strings to the
default JSON parser will fail at runtime. |
+| `JsonSerializer.DEFAULT.serialize(bean)` produced strict JSON (was
historically inconsistent depending on builder flags) | Always strict JSON
(double-quoted keys / strings, no trailing commas, no comments) | Behavioral.
For JSON5 output use `Json5Serializer.DEFAULT.serialize(...)`. |
+| `BeanContext.beanToStringSerializer` used `JsonSerializer` for the
bean-to-string fallback | Uses `Json5Serializer` (more human-readable). |
Source-compatible — affects only the `toString()` shape of `BeanMap` and
related types. |
+| `ComboRoundTrip_Tester` used `json()` for both strict-JSON and JSON5 outputs
| Use `json()` for strict JSON, `json5()` for JSON5 — they are now distinct
configurations | Test-only impact. |
+
+## Sorted Bean Properties by Default
+
+Bean properties are now serialized in **alphabetical order by default** across
all serializers. Previously the default was natural JVM order and opting in to
sorted output required explicit configuration. The `sortProperties` API is
fully removed and replaced by `unsortedProperties` with inverted semantics.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@Bean(sort=true)` — opt-in to sorted output | `@Bean(unsorted=true)` —
opt-out of default sorted output | Hard rename with inverted semantics.
Removing both attributes leaves properties sorted (was: unsorted). |
+| `BeanContext.Builder.sortProperties()` / `sortProperties(boolean)` |
`BeanContext.Builder.unsortedProperties()` / `unsortedProperties(boolean)` |
Hard removal. Inverted semantics. |
+| `BeanContext.Builder.sortProperties(Class<?>...on)` |
`BeanContext.Builder.unsortedProperties(Class<?>...on)` | Same. |
+| `BeanContextable.Builder.sortProperties()` / `sortProperties(Class<?>...on)`
| `unsortedProperties()` / `unsortedProperties(Class<?>...on)` | Mirrored on
every serializer / parser builder. |
+| `BeanFilter.Builder.sortProperties()` / `sortProperties(boolean)` |
`BeanFilter.Builder.unsortedProperties()` | Per-bean-filter opt-out. |
+| `BeanFilter.isSortProperties()` / `BeanSession.isSortProperties()` /
`BeanMeta.isSortProperties()` | `BeanFilter.isUnsortedProperties()` /
`BeanSession.isUnsortedProperties()` / (internal field renamed on `BeanMeta`) |
Inverted semantics. |
+| All ~55 serializer / parser builder `sortProperties()` /
`sortProperties(Class<?>...on)` overrides (JSON, XML, HTML, YAML, UON, URL
Encoding, CSV, OpenAPI, MsgPack, CBOR, BSON, RDF, etc.) | All replaced by
`unsortedProperties()` / `unsortedProperties(Class<?>...on)` overrides | Hard
rename across the entire builder hierarchy. |
+| Static `DEFAULT_SORTED` constants on `JsonSerializer` etc. | Removed |
Redundant — `DEFAULT` is now sorted. |
+
+## Native `Iterator` / `Iterable` / `Stream` / `Enumeration` Serialization
+
+`Iterator`, non-`Collection` `Iterable`, `Enumeration`, and
`java.util.stream.Stream` are now serialized directly as arrays without
`IteratorSwap` / `EnumerationSwap` materializing them to a `LinkedList` first.
JSON / XML / UON / URL Encoding / OpenAPI write elements lazily; MsgPack / HTML
/ CSV collect to a `List` internally because the format requires up-front
knowledge of size or column headers.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.swaps.IteratorSwap` | Removed. `Iterator` handled
natively. | If you registered this swap explicitly, drop the registration. |
+| `org.apache.juneau.swaps.EnumerationSwap` | Removed. `Enumeration` handled
natively. | Same. |
+| `Stream<T>` and non-`Collection` `Iterable<T>` had no serialization support
— fell through to bean serialization (or threw) | Serialized directly as
arrays. `Stream<T>` is auto-closed via try-with-resources after iteration. |
Additive new behavior. |
+| `Supplier<T>` (standard JDK, not `BeanSupplier`) inside a bean property
serialized as a bean wrapping the supplier itself | Transparently unwrapped by
serializers as a single lazy value, recursively up to depth 10. | Behavioral.
Affects any place a `Supplier<T>` is exposed as a bean property. |
+| `ClassMeta` had no `isIterator()` / `isIterable()` / `isStream()` /
`isStreamable()` predicates | New predicates plus new `Category` enum values
`ITERATOR`, `ITERABLE`, `STREAM` | New API. POJO categories table moves these
out of "swapped objects" (group 4b) into a new sequence-types group alongside
Collections / arrays. |
+
+## HTTP Annotation Moves and Attribute Removals
+
+The HTTP parameter annotations (`@Header`, `@Query`, `@Path`, `@Content`,
`@FormData`, `@Request`, `@Response`, `@StatusCode`, `@PathRemainder`,
`@HasQuery`, `@HasFormData`, `@Contact`, `@License`, `@Tag`) along with the
constants (`CollectionFormatType`, `FormatType`, `ParameterType`) and the
`httppart.bean` package (`RequestBeanMeta`, `RequestBeanPropertyMeta`,
`ResponseBeanMeta`, `ResponseBeanPropertyMeta`, `MethodInfoUtils`) have
**moved** from `juneau-marshall` into `juneau-rest-com [...]
+
+Several attributes on these annotations have been **removed without
deprecation shims**:
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@Query(serializer=..., parser=...)` / `@Header(serializer=..., parser=...)`
/ `@FormData(serializer=..., parser=...)` / `@Path(serializer=..., parser=...)`
/ `@PathRemainder(serializer=..., parser=...)` / `@Request(serializer=...,
parser=...)` / `@Response(serializer=..., parser=...)` | New
`@HttpPartMarshalling(serializer=..., parser=...)` annotation (in
`juneau-marshall`, package `org.apache.juneau.httppart`) applied alongside the
HTTP annotation | Hard removal. The HTTP annotations [...]
+| `@Repeatable` on every HTTP annotation (`@Query`, `@Header`, `@FormData`,
`@Path`, `@Content`, `@PathRemainder`, `@Request`, `@Response`, `@StatusCode`,
`@HasQuery`, `@HasFormData`) | Not repeatable — author one annotation per
element | Hard removal. Dynamic annotation stacking is no longer supported. |
+| `on()` / `onClass()` attributes on all HTTP annotations | Removed — apply
the annotation directly to the target rather than via `on()` / `onClass()`
proxies | Hard removal. |
+| `@ContextApply` on HTTP annotations | Removed — the annotation no longer
participates in the context-apply pass | Hard removal. |
+| 10 `XAnnotation` companion classes: `ContentAnnotation`,
`FormDataAnnotation`, `HasFormDataAnnotation`, `HasQueryAnnotation`,
`HeaderAnnotation`, `PathAnnotation`, `PathRemainderAnnotation`,
`QueryAnnotation`, `RequestAnnotation`, `StatusCodeAnnotation` | Removed (used
by the deleted `@ContextApply` plumbing) | Hard removal. `ContactAnnotation`,
`LicenseAnnotation`, `TagAnnotation`, and `ResponseAnnotation` remain (still
consumed by Swagger generation utilities). |
+| `org.apache.juneau.ng.http.remote.Body` / `Header` / `Path` / `Query` *(9.5
early snapshot only)* | `@org.apache.juneau.http.annotation.Content` / `Header`
/ `Path` / `Query` | The NG duplicate annotations were deleted; the canonical
`RestClient` (formerly `NgRestClient`) uses the standard annotations. |
+
+## Request Attributes vs Session Properties Separation
+
+Request attributes and parser / serializer session properties are now
completely separate concepts. Previously, `defaultRequestAttributes` from
`@Rest` / `@RestOp` were automatically merged into session properties,
conflating two distinct concerns.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@Rest(defaultRequestAttributes={"key: value"})` flowed automatically into
the parser / serializer session for every request | `defaultRequestAttributes`
populates only `RequestAttributes` (accessed via
`RestRequest.getAttributes()`). Session properties must be set explicitly via
the new programmatic API on `RestRequest`. | **Behavioral break.** Resource
code that relied on the implicit merge must explicitly bridge it — see the rows
below. |
+| n/a | `RestRequest.setSerializerSessionProperty(String, Object)` /
`setParserSessionProperty(String, Object)` | Set per-request session properties
programmatically. Typically called from `@RestPreCall` / `@RestPostCall` or
inside the REST method before parsing / serialization. |
+| n/a | `RestRequest.setSerializerSessionProperties(Map<String,Object>)` /
`setParserSessionProperties(Map<String,Object>)` | Set multiple session
properties at once. Use
`req.setSerializerSessionProperties(req.getAttributes().asMap())` to restore
the pre-9.5 implicit-merge behavior. |
+
+## REST Session-Option Allowlist Refactor
+
+The standalone session-option allowlist machinery has been replaced by a
`noInherit` attribute on the existing `@Rest` / `@RestOp` group of annotations.
Programmatic `RestContext.Builder` / `RestOpContext.Builder` allowlist setters
are gone (the builders themselves are gone — see the top of this file).
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `@NoInherit` standalone annotation | `noInherit` attribute on `@Rest` /
`@RestOp` / `@RestGet` / `@RestPost` / `@RestPut` / `@RestPatch` /
`@RestDelete` / `@RestOptions` | Hard removal. Use the annotation attribute
directly, e.g. `@RestGet(allowedSerializerOptions="myOpt",
noInherit="allowedSerializerOptions")`. Supported names include
`allowedParserOptions` and `allowedSerializerOptions` (case-insensitive; SVL
and comma-separated tokens expand). |
+| `org.apache.juneau.rest.util.StringTokenSet` (REST-server allowlist driver)
| Internal merge handled by `RestSessionOptionAllowlistMerge` | Internal class
— only relevant if reflectively referenced. The `juneau-commons`
`StringTokenSet` survives as a general-purpose token-set utility. |
+| `RestAllowedSerializerOptions` / `RestAllowedParserOptions` | Removed — keys
live on `@Rest` / `@RestOp(allowedSerializerOptions / allowedParserOptions)`
annotation attributes | Hard removal. |
+| `RestAllowedSessionOptionsMerge` / `NoInheritUtils` | Removed | Internal
merge helpers — no replacement needed. |
+| `RestContext.Builder.appendAllowedSerializerOptions(...)` /
`appendAllowedParserOptions(...)` / `allowedSerializerOptions(...)` /
`allowedParserOptions(...)` setters | Express via
`@Rest(allowedSerializerOptions=..., allowedParserOptions=...)` annotation
attributes | The programmatic builder setters are gone with the builder itself.
|
+| `RestOpContext.Builder.appendAllowed*()` / `allowedSerializerOptions(...)` /
`allowedParserOptions(...)` setters | Express via
`@RestOp(allowedSerializerOptions=..., allowedParserOptions=...)` annotation
attributes | Same. |
+| `RestSessionOptionWire` static literals | `RestSharedConstants` in
`juneau-rest-common` — `HEADER_JuneauSerializerOptions`,
`HEADER_JuneauParserOptions`, `QUERY_juneauSerializerOptions`,
`QUERY_juneauParserOptions` | Class rename. |
+| `RestSessionOptionsSettings` static literals | `RestServerConstants` in
`juneau-rest-server` — `SETTING_sessionOptions_rejectWhenAllowlistEmpty`,
`SETTING_sessionOptions_failOnInvalidAllowlistEntry`, etc. | Class rename. |
+
+## Legacy `cp` Bean-Store Classes Removed (TODO-26)
+
+The pre-existing legacy bean-store / bean-creator classes in
`org.apache.juneau.cp` have been deleted. The replacement types live in
`org.apache.juneau.commons.inject` (introduced in TODO-24 / TODO-15).
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.cp.BasicBeanStore` |
`org.apache.juneau.commons.inject.BasicBeanStore` (renamed from
`BasicBeanStore2`) | Constructor: `BasicBeanStore.create().build()` → `new
BasicBeanStore()`. Static `INSTANCE` field preserved.
`BasicBeanStore.of(parent)` → `new BasicBeanStore(parent)`. |
+| `org.apache.juneau.cp.BeanCreator` |
`org.apache.juneau.commons.inject.BeanInstantiator` | Hard rename.
`BeanCreator.of(MyBean.class, store)` → `BeanInstantiator.of(MyBean.class,
store)`. |
+| `creator.arg(Type.class, value)` | `instantiator.addBean(Type.class, value)`
| Method rename. |
+| `creator.type(Impl.class)` / `creator.impl(instance)` / `creator.run()` |
`instantiator.type(Impl.class)` / `instantiator.impl(instance)` /
`instantiator.run()` | Method names unchanged. |
+| `creator.orElse(default)` | `instantiator.asOptional().orElse(default)` |
Two-step replacement. |
+| `org.apache.juneau.cp.BeanBuilder` (base class for domain fluent builders) |
`org.apache.juneau.commons.inject.BeanInstantiator` plus native builder fields
on the domain types | Hard removal. Domain builders now use native fields
instead of extending `BeanBuilder`. |
+| `org.apache.juneau.cp.BeanCreateMethodFinder` |
`BeanStore.createBeanFromMethod(type, obj, predicate)` | Hard rename —
method-on-store rather than free-standing finder class. |
+| `org.apache.juneau.cp.ContextBeanCreator` | Retained at
`org.apache.juneau.cp.ContextBeanCreator` | **Not** removed. Solves a different
problem (persistent `Context.Builder` holder for repeated annotation
application). |
+| `org.apache.juneau.commons.inject.CreatableBeanStore` | Removed | Unused
internal interface. `WritableBeanStore` is the canonical write-capable
interface. |
+
+## Bean-Store Convenience Renames
+
+The `Xxx2`-suffix transitional types have been promoted to the canonical name:
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.commons.inject.BasicBeanStore2` |
`org.apache.juneau.commons.inject.BasicBeanStore` | Hard rename. The legacy
`org.apache.juneau.cp.BasicBeanStore` is deleted — see "Legacy `cp` Bean-Store
Classes Removed (TODO-26)" above. |
+| `org.apache.juneau.rest.springboot.SpringBeanStore2` |
`org.apache.juneau.rest.springboot.SpringBeanStore` | Hard rename. The old name
is removed. |
+| Several builder fields/constructors typed as `WritableBeanStore` on
`EncoderSet.Builder`, `SerializerSet.Builder`, `ParserSet.Builder`,
`ResponseProcessorList.Builder`, `RestOpArgList.Builder`,
`RestMatcherList.Builder`, `RestGuardList.Builder`,
`RestConverterList.Builder`, `RestOperations.Builder`, `RestChildren.Builder` |
Narrowed to `BeanStore` (read-only) | Source-compatible — `WritableBeanStore
extends BeanStore`, so existing call sites pass through unchanged. The narrower
type cl [...]
+
+## Swagger v2: `ParamInfo` → `ParameterInfo`
+
+The legacy `ParamInfo` type in the Swagger v2 bean module
(`org.apache.juneau.bean.swagger.ParamInfo`) has been fully replaced with
`ParameterInfo`. All references in `juneau-bean-swagger` have been migrated.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `org.apache.juneau.bean.swagger.ParamInfo` |
`org.apache.juneau.bean.swagger.ParameterInfo` | Hard rename. Update imports
and references. |
+
+## Multi-Key Cache and `ConcurrentHashMapXKey` Refactor (9.2.0)
+
+`juneau-commons` introduced a dedicated `Cache<...>` family that separates
caching concerns from the base concurrent maps. The base maps were renamed for
naming-convention consistency, and their constructors no longer accept caching
parameters.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `Concurrent2KeyHashMap` | `ConcurrentHashMap2Key` | Hard rename for
naming-convention consistency. |
+| `Concurrent3KeyHashMap` | `ConcurrentHashMap3Key` | Same. |
+| `Concurrent4KeyHashMap` | `ConcurrentHashMap4Key` | Same. |
+| `Concurrent5KeyHashMap` | `ConcurrentHashMap5Key` | Same. |
+| `new ConcurrentXKeyHashMap(boolean disabled, Function supplier, ...)`
constructors carrying caching state | Pure multi-key concurrent maps (`extends
ConcurrentHashMap<TupleX<...>, V>`); for caching use the new sibling
`Cache<K,V>` / `Cache2<K1,K2,V>` / `Cache3` / `Cache4` / `Cache5`
builder-pattern API (`Cache2.of(K1.class, K2.class,
V.class).maxSize(...).supplier(...).build()`) | Code using only the multi-key
map needs a class-name update. Code using `disabled` / `supplier` parameters
[...]
+| Null key in `ConcurrentXKeyHashMap.get(...)` / `put(...)` previously
bypassed the cache silently | Throws `IllegalArgumentException` | **Hard
behavioral break.** |
+
+## Module Rename: `juneau-all` → `juneau-shaded-all` (9.2.0)
+
+The previous `juneau-all` Maven module has been **removed** in favor of
`juneau-shaded-all`, part of the new `juneau-shaded` family of shaded (uber)
JAR artifacts. No code changes are required — all imports and APIs are
unchanged.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `<artifactId>juneau-all</artifactId>` |
`<artifactId>juneau-shaded-all</artifactId>` | Hard rename. Consider the more
specific shaded artifacts (`juneau-shaded-core`, `juneau-shaded-rest-client`,
`juneau-shaded-rest-server`, `juneau-shaded-rest-server-springboot`) if you
don't need the full framework. |
+
+## Miscellaneous Utility Removals
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `Tuple2Function` / `Tuple3Function` / `Tuple4Function` / `Tuple5Function` |
`Function2` / `Function3` / `Function4` / `Function5` | Hard rename — API is
equivalent (same arity, same `apply(...)` signature). |
+| `Console.format(String, Object...)` | `Utils.f(String format, Object...
args)` | Hard removal — duplicated functionality. `Console.err(...)` /
`Console.out(...)` are unchanged. |
+| `BasicRuntimeException` (concrete subclass used to wrap arbitrary checked
exceptions) | `ThrowableUtils.runtimeException(...)` (with optional `Throwable
cause` overload) | Retired in favor of the `ThrowableUtils` factory. Use
`runtimeException(msg, args)` or `runtimeException(cause, msg, args)`. New
helpers in `ThrowableUtils`: `unsupportedOp(String, Object...)`,
`ioException(String, Object...)`, plus cause-accepting overloads of
`illegalArg(...)` / `runtimeException(...)`. |
+| `ArrayUtils` static methods | `CollectionUtils` static methods (same
signatures: `last()`, `append()`, `combine()`, `indexOf()`, `toList()`,
`contains()`, etc.) | `ArrayUtils` is now `@Deprecated` and delegates to
`CollectionUtils`. Migrate at your convenience; both will continue to work
during the deprecation period. |
+| `ResettableSupplier` | `OptionalSupplier` | Class rename. |
+
+## BCT: `AssertionArgs` Removed
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `AssertionArgs.withConverter(customConverter).assertBean(expected, actual,
"path")` | `setConverter(() -> customConverter); assertBean(expected, actual,
"path"); resetConverter();` | Hard removal of the `AssertionArgs` builder.
Assertion methods now take an optional leading `Supplier<String>` parameter for
custom messages: `assertBean(() -> "custom msg", expected, actual,
"propertyPath")`. The default thread-local converter supplier is
`BasicBeanConverter.DEFAULT`. |
+
+## OpenAPI 3.1 Emission + `@Rest(apiFormat=…)` Knob (TODO-63)
+
+OpenAPI 3.1 emission is new in 9.5; existing Swagger v2 emission is unchanged.
The new `@Rest(apiFormat=…)` attribute selects which spec format the canonical
`/api/*` endpoint serves and whether the new `/openapi/*` sibling is mounted.
The default is `"swagger"` (back-compat).
+
+| Old | New | Notes |
+|-----|-----|-------|
+| Resource exposed only Swagger v2 at `/api/*`. No way to opt into OpenAPI 3.1
emission. | `@Rest(apiFormat="openapi")` opts the resource into OpenAPI 3.1 +
Redoc on `/openapi/*` (and 404s `/api/*`). `@Rest(apiFormat="both")` mounts
Swagger v2 + Swagger UI on `/api/*` AND OpenAPI 3.1 + Redoc on `/openapi/*`.
Default `"swagger"` is unchanged behavior. | System-property override:
`juneau.rest.apiFormat`. Resolution precedence: annotation → system property →
default. Recognized values are ` [...]
+| `BasicRestOperations.getSwagger(RestRequest)` and (new)
`getOpenApi(RestRequest)` were going to be abstract methods that every
implementer had to override. | Both are now `default` methods on
`BasicRestOperations` that consult `RestContext.getApiFormat()` and either
delegate to `RestRequest.getSwagger()` / `getOpenApi()` or throw `NotFound`
based on the resolved format. | Subclasses (`BasicRestObject`,
`BasicRestServlet`, `BasicSpringRestServlet`) no longer override these methods.
User [...]
+| `RestRequest.getSwagger()` was the only way to ask the server for its
self-described API. | `RestRequest.getOpenApi()` provides the OpenAPI 3.1
sibling. `RestContext.getOpenApi(Locale)` and
`RestContext.getOpenApiProvider()` mirror the Swagger getters. | The OpenAPI
3.1 document is generated by transforming the Swagger 2.0 emission to OpenAPI
3.1 JSON, so every Swagger-aware annotation (`@Schema`, `@Content`,
`@StatusCode`, etc.) round-trips with no source changes. |
+
+Reference: see the per-module section in the 9.5 release notes (`###
juneau-rest-server` → `OpenAPI 3.1 Emission + apiFormat Knob (TODO-63)`).
+
+<!-- Migration guide complete for the 9.1 → 9.5 jump. Add new entries here as
further 9.5.x
+or 9.6 breaking changes land. -->
diff --git a/sidebars.ts b/sidebars.ts
index a871cd04b5..8c1be7b83b 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1327,6 +1327,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.07.HandlingFormPosts',
label: '10.7. Handling
Form Posts',
},
+ {
+ type: 'doc',
+ id:
'topics/10.08.RestServerSse',
+ label: '10.8.
Server-Sent Events',
+ },
{
type: 'doc',
id:
'topics/10.08.Guards',
@@ -1471,6 +1476,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.20.HttpStatusCodes',
label: '10.20. HTTP
Status Codes',
},
+ {
+ type: 'doc',
+ id:
'topics/10.20a.RestServerProblemDetails',
+ label: '10.20a. RFC
7807 / 9457 Problem Details',
+ },
{
type: 'doc',
id:
'topics/10.21.BuiltInParameters',