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 40e125a945 docs: API-docs mixin pack topic page + parent-chain 
aggregation + 9.5 release notes (FINISHED-74)
40e125a945 is described below

commit 40e125a945ed46e0e1c3e67c2023b35dcbd91f89
Author: James Bognar <[email protected]>
AuthorDate: Sun May 24 14:48:44 2026 -0400

    docs: API-docs mixin pack topic page + parent-chain aggregation + 9.5 
release notes (FINISHED-74)
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/9.5.0.md                     |  77 +++++--
 pages/topics/10.07a.RestServerComposition.md     | 129 +++++++++++
 pages/topics/10.16.02.BasicRestServletSwagger.md | 215 +++++++++--------
 pages/topics/10.16.02a.ApiDocsMixins.md          | 282 +++++++++++++++++++++++
 pages/topics/23.01.V9.5-migration-guide.md       |  11 +-
 sidebars.ts                                      |  25 +-
 6 files changed, 615 insertions(+), 124 deletions(-)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index df8ea7cfc1..18b9aab87a 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 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 9.5.0 is a minor release with native OpenAPI 3.1 emission (alongside 
Swagger v2, composed via the new four-class `org.apache.juneau.rest.docs` mixin 
pack — `BasicSwaggerResource` / `BasicSwaggerUiResource` / 
`BasicOpenApiResource` / `BasicRedocResource` — that replaces the 
previously-considered `apiFormat` string knob), native TOML and YAML support, 
BSON (Binary JSON) support for MongoDB-interoperable binary serialization, CBOR 
(Concise Binary Object Representation) per RFC 8949 f [...]
 
 ### juneau-marshall
 
@@ -2150,31 +2150,80 @@ literals continue to behave exactly as they did under 
FINISHED-72 (the annotatio
 on its own, element-for-element). Pre-9.5.0 users who already adopted 
`@Rest(paths=...)` see no
 behavior change unless they introduce an SVL marker or an embedded comma in an 
element.
 
-#### OpenAPI 3.1 Emission + `apiFormat` Knob (TODO-63)
+#### OpenAPI 3.1 Emission + API-docs Mixin Pack (TODO-63 + TODO-74)
 
-`juneau-rest-server` now ships first-class OpenAPI 3.1 document generation 
alongside the existing Swagger v2 path:
+`juneau-rest-server` now ships first-class OpenAPI 3.1 document generation 
alongside the existing Swagger v2 path, with the public surface composed via 
four small `@Rest(mixins=...)` mixins instead of a `apiFormat` string knob:
+
+**OpenAPI 3.1 emission core (TODO-63):**
 
 - 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.
+- **`components.schemas` reuse.** `BasicOpenApiProviderSession` 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>` k [...]
+- **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 (mounted via the api-docs mixin pack), and (c) the `/openapi` endpoint 
served with `Accept: application/yaml`. Each path asserts structural equality 
across `openapi`, `info`, `servers`, `paths`, and `components.schemas`.
+
+**API-docs mixin pack (TODO-74):**
+
+The api-docs surface is now four sibling `@Rest(mixins=...)` mixins in the new 
`org.apache.juneau.rest.docs` package — composition replaces the `apiFormat` 
string knob:
+
+| Mixin | Default `paths` | Owns |
+|---|---|---|
+| `BasicSwaggerResource` | `/api` | Swagger v2 spec + `SwaggerUI` swap. 
Standard content negotiation. |
+| `BasicSwaggerUiResource` | `/swagger` | HTML-first mount; 
`@Rest(defaultAccept="text/html")` so bare browser requests render the 
Swagger-UI view. Transitively pulls in `BasicSwaggerResource`. |
+| `BasicOpenApiResource` | `/openapi`, `/openapi.json`, `/openapi.yaml` | 
OpenAPI 3.1 spec + `RedocUI` swap on `/openapi`. The `.json` and `.yaml` paths 
are **format-pinned** — they ignore the request `Accept` header and always emit 
the file extension's wire format (implemented via 
`RestResponse.getDirectWriter(...)`, not content negotiation, so the pin 
survives `Accept: text/html`). Mixin declares 
`@Rest(serializers={YamlSerializer.class})` so YAML is available for the 
`/openapi` conten [...]
+| `BasicRedocResource` | `/redoc` | HTML-first mount; 
`@Rest(defaultAccept="text/html")`. Transitively pulls in 
`BasicOpenApiResource`. |
+
+Pick the mixins you want and the URL surface follows. Three common 
compositions:
 
-The new `@Rest(apiFormat="…")` attribute selects which spec format the 
canonical `/api/*` endpoint serves (and whether the new `/openapi/*` sibling is 
mounted):
+```java
+// OpenAPI 3.1 spec only — /openapi, /openapi.json, /openapi.yaml.
+@Rest(path="/api", mixins=BasicOpenApiResource.class)
+public class ApiResource extends RestServlet { ... }
+
+// OpenAPI 3.1 + Redoc UI — pulls BasicOpenApiResource in transitively.
+@Rest(path="/api", mixins=BasicRedocResource.class)
+public class ApiResource extends RestServlet { ... }
+
+// Everything (post-9.5.0 default for BasicRestServlet / BasicRestObject).
+@Rest(path="/api", mixins={BasicSwaggerUiResource.class, 
BasicRedocResource.class})
+public class ApiResource extends RestServlet { ... }
+// Serves: /api, /swagger, /openapi, /openapi.json, /openapi.yaml, /redoc
+```
 
-| 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 |
+Transitive resolution dedupes by class identity (`LinkedHashSet` in the 
framework's `collectRestMixins(...)` walk), so listing both UI mixins doesn't 
double-instantiate the spec mixins. Parent-chain aggregation also works — a 
child class's `@Rest(mixins=...)` unions with the parent's.
 
-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`).
+`BasicRestServlet` and `BasicRestObject` migrated to declare 
`@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})` so 
subclasses get all six api-docs URLs by default. Apps that want to opt out 
(e.g. to mount only OpenAPI) can use `@Rest(noInherit={"mixins"}, mixins=...)` 
to cut off the inherited docs mixins.
 
-Three additional pieces close out the TODO-63 scope:
+**Breaking changes (TODO-74):**
+
+| Removed | Replacement |
+|---|---|
+| `@Rest(apiFormat="swagger" \| "openapi" \| "both")` | Pick mixins. Both 
`/api` and `/openapi` mount when both spec mixins are present. |
+| `juneau.rest.apiFormat` system property | Removed. No replacement — apps 
that toggled this at runtime should pick mixins at compile time. |
+| `RestContext.getApiFormat()` | Removed. |
+| `RestServerConstants.API_FORMAT_SWAGGER` / `_OPENAPI` / `_BOTH` / 
`SYSPROP_apiFormat` / `PROPERTY_apiFormat` | Removed. |
+| `BasicRestOperations.getSwagger(...)` / `getOpenApi(...)` | Removed from the 
interface. `BasicRestServlet` / `BasicRestObject` mount these via 
`BasicSwaggerResource` / `BasicOpenApiResource` instead. |
+| `BasicGroupOperations.getChildrenSwagger(...)` / `getChildrenOpenApi(...)` 
(the `?Swagger` / `?OpenApi` query mirrors on group resources) | Removed. Hit 
`/api`, `/swagger`, `/openapi`, or `/redoc` directly — they are always mounted 
on `BasicRestServletGroup` / `BasicRestObjectGroup`. |
+| `BasicGroupOperations.HasSwaggerQueryParam` / `HasOpenApiQueryParam` matcher 
inner classes | Removed (no consumers left). |
+
+Migration sketch:
+
+```java
+// 9.4.x:
+@Rest(apiFormat="openapi")
+public class MyApi extends BasicRestServlet { ... }
+
+// 9.5.0:
+@Rest(noInherit={"mixins"}, mixins=BasicRedocResource.class)   // opt out of 
Swagger
+public class MyApi extends BasicRestServlet { ... }
+// or extend RestServlet directly and pick what you want:
+@Rest(mixins=BasicRedocResource.class)
+public class MyApi extends RestServlet implements BasicUniversalConfig { ... }
+```
 
-- **`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`.
+Apps that subclass `BasicRestServlet` / `BasicRestObject` without setting 
`apiFormat` or relying on the query mirrors get the new behavior (six api-docs 
URLs instead of one) automatically.
 
 #### Server-side SSE Helpers (TODO-62)
 
diff --git a/pages/topics/10.07a.RestServerComposition.md 
b/pages/topics/10.07a.RestServerComposition.md
index d409774938..18130e229d 100644
--- a/pages/topics/10.07a.RestServerComposition.md
+++ b/pages/topics/10.07a.RestServerComposition.md
@@ -537,8 +537,137 @@ is no per-operation "imported from X" tag in the spec. If 
you need that distinct
 consumers, declare the addon as a separate child resource 
(`@Rest(children=...)`) so it gets its
 own Swagger / OpenAPI scope.
 
+## Parent-chain mixin aggregation
+
+`@Rest` is `@Inherited`, so a mixin set declared on a parent class flows 
through to subclasses
+just like the rest of the `@Rest` attributes. The aggregator collects every 
`@Rest(mixins=...)`
+in the resource class hierarchy in **parent-to-child** order, with the 
parent's mixins inserted
+first and the child's appended after. The resulting list is funneled through a
+`LinkedHashSet` keyed by class identity, so:
+
+- **Order is preserved** in the order Juneau encountered each mixin (parent's 
mixins first,
+  then the child's). The mixin walk visits them in that order, so registration 
order — and
+  therefore importer-wins precedence for any collisions between two mixins — 
is deterministic
+  across rebuilds.
+- **Duplicates are deduped silently.** Declaring the same mixin class on both 
parent and child
+  is harmless — `LinkedHashSet` only keeps the first occurrence, so the mixin 
is constructed
+  once and grafted once.
+
+### Example: parent declares a mixin, child inherits
+
+```java
+@Rest(mixins = BasicRedocResource.class)
+public abstract class CommonApi extends RestServlet {
+    @RestGet("/health") public String health() { return "ok"; }
+}
+
+@Rest                              // No mixins declared — inherits parent's 
by Java @Inherited.
+public class TenantApi extends CommonApi {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+```
+
+`TenantApi` resolves to mixin set `[BasicOpenApiResource, BasicRedocResource]` 
— the parent's
+`BasicRedocResource` flows in via `@Inherited`, and transitive resolution then 
walks
+`BasicRedocResource`'s own `@Rest(mixins=BasicOpenApiResource.class)` to bring 
in the spec
+mixin. Result: `TenantApi` serves `/health`, `/items`, `/openapi`, 
`/openapi.json`,
+`/openapi.yaml`, and `/redoc`.
+
+### Example: parent + child both declare mixins (union)
+
+```java
+@Rest(mixins = BasicSwaggerResource.class)
+public abstract class CommonApi extends RestServlet { ... }
+
+@Rest(mixins = BasicHealthResource.class)
+public class TenantApi extends CommonApi { ... }
+```
+
+`TenantApi` resolves to mixin set `[BasicSwaggerResource, 
BasicHealthResource]` — parent-first
+ordering preserved, both mixins live, both grafted. The child does not need to 
redeclare the
+parent's mixin to keep it.
+
+### Example: parent + child declare the same mixin (dedupe)
+
+```java
+@Rest(mixins = BasicHealthResource.class)
+public abstract class CommonApi extends RestServlet { ... }
+
+@Rest(mixins = {BasicHealthResource.class, BasicSwaggerResource.class})
+public class TenantApi extends CommonApi { ... }
+```
+
+`TenantApi` resolves to mixin set `[BasicHealthResource, 
BasicSwaggerResource]`. The
+`BasicHealthResource` declaration on the child is harmless — `LinkedHashSet` 
dedupes by class
+identity, so the mixin is constructed once and grafted once. The child can 
list the parent's
+mixin alongside its own without worrying about double-registration.
+
+### Opt-out: `@Rest(noInherit={"mixins"})`
+
+Sometimes a child needs to drop the parent's mixins entirely — e.g. a subclass 
that wants to
+substitute a different api-docs surface, or a custom subclass of 
`BasicRestServlet` that wants
+only the OpenAPI half:
+
+```java
+@Rest(noInherit = {"mixins"}, mixins = BasicRedocResource.class)
+public class OpenApiOnly extends BasicRestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+```
+
+`noInherit = {"mixins"}` tells the aggregator to skip the parent's 
`@Rest(mixins=...)` when
+walking the inheritance chain. `OpenApiOnly` therefore mounts `/openapi`, 
`/openapi.json`,
+`/openapi.yaml`, `/redoc`, and `/items` — but **not** `/api`, `/swagger`, or 
any of
+`BasicRestServlet`'s default Swagger surface.
+
+`noInherit` accepts the same property names that `@Rest` exposes (e.g. 
`"converters"`,
+`"guards"`, `"mixins"`, `"responseProcessors"`), so you can cut off 
inheritance per-property
+without dropping everything else.
+
+### Interaction with transitive mixin resolution
+
+Parent-chain aggregation and transitive mixin resolution are orthogonal — both 
apply, and the
+order is well-defined:
+
+1. The aggregator walks the resource class hierarchy parent-to-child, 
collecting every
+   `@Rest(mixins=...)` declaration into the initial mixin set.
+2. The transitive walk then visits each mixin in that set and recursively 
pulls in any
+   mixins the mixin itself declares via its own `@Rest(mixins=...)`.
+3. `LinkedHashSet` deduping runs on the union, so a mixin reachable via 
multiple paths
+   (parent-direct, child-direct, transitive-from-parent's-mixin, 
transitive-from-child's-mixin)
+   is still constructed once.
+
+Worked example:
+
+```java
+@Rest(mixins = BasicOpenApiResource.class)
+public abstract class CommonApi extends RestServlet { ... }
+
+@Rest(mixins = BasicRedocResource.class)
+public class TenantApi extends CommonApi { ... }
+```
+
+`TenantApi` resolves to mixin set `[BasicOpenApiResource, BasicRedocResource]`:
+
+- Parent contributes `BasicOpenApiResource` directly.
+- Child contributes `BasicRedocResource` directly.
+- Transitive walk visits `BasicRedocResource` and follows its
+  `@Rest(mixins=BasicOpenApiResource.class)` — but `BasicOpenApiResource` is 
already in the
+  set, so the dedupe drops the second occurrence on the floor. Net effect: the 
spec mixin is
+  grafted once, mounted at `/openapi/*`, and shared between both UI mounts.
+
+This is the pattern the api-docs mixin pack relies on to make
+`@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})` on 
`BasicRestServlet`
+work correctly. See [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) for the 
full worked
+example, including the `noInherit={"mixins"}` opt-out for users who want to 
drop one half of
+the inherited pack.
+
 ## See also
 
+- [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) — the canonical worked 
example of mixin
+  composition: four single-purpose api-docs mixins (`BasicSwaggerResource`,
+  `BasicSwaggerUiResource`, `BasicOpenApiResource`, `BasicRedocResource`) 
composed into
+  `BasicRestServlet` with transitive resolution + parent-chain aggregation.
 - [Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts) — the 
per-mixin `RestContext`
   inheritance model, `noInherit` opt-out, hook dual-firing, flat-inheritance 
rule, and
   mixin-vs-child divergence.
diff --git a/pages/topics/10.16.02.BasicRestServletSwagger.md 
b/pages/topics/10.16.02.BasicRestServletSwagger.md
index 5099fe1afb..87731dafca 100644
--- a/pages/topics/10.16.02.BasicRestServletSwagger.md
+++ b/pages/topics/10.16.02.BasicRestServletSwagger.md
@@ -3,116 +3,141 @@ 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 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`:
+> **Migrating from 9.4.x?** The `@Rest(apiFormat=…)` annotation member, the
+> `juneau.rest.apiFormat` system property, the `RestContext.getApiFormat()` 
accessor, and the
+> `?Swagger` / `?OpenApi` query mirrors on group resources are all **removed 
in 9.5.0**. Both
+> Swagger v2 and OpenAPI 3.1 are now always mounted via the four-class 
api-docs mixin pack on
+> `BasicRestServlet` / `BasicRestObject`. See the
+> [V9.5 Migration Guide](/docs/topics/V95MigrationGuide) for the full removal 
table, and
+> [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) for the new composition 
story.
+
+Any subclass of 
[`BasicRestServlet`](/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServlet.html)
+or 
[`BasicRestObject`](/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObject.html)
 gets
+**six** auto-generated API-documentation endpoints by default:
+
+| URL | Format | UI swap |
+|---|---|---|
+| `GET /api` | Swagger v2 spec | `SwaggerUI` swap for `Accept: text/html` |
+| `GET /swagger` | Same Swagger v2 spec at an HTML-first URL | `SwaggerUI` 
swap for bare browser GET |
+| `GET /openapi` | OpenAPI 3.1 spec | `RedocUI` swap for `Accept: text/html` |
+| `GET /openapi.json` | OpenAPI 3.1 spec | **Format-pinned JSON** (ignores 
`Accept`) |
+| `GET /openapi.yaml` | OpenAPI 3.1 spec | **Format-pinned YAML** (ignores 
`Accept`) |
+| `GET /redoc` | Same OpenAPI 3.1 spec at an HTML-first URL | `RedocUI` swap 
for bare browser GET |
+
+The six endpoints come from a four-class mixin pack:
+[`BasicSwaggerResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicSwaggerResource.html),
+[`BasicSwaggerUiResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicSwaggerUiResource.html),
+[`BasicOpenApiResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicOpenApiResource.html),
+and 
[`BasicRedocResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicRedocResource.html).
+`BasicRestServlet` declares:
 
 ```java
-@RestGet(
-    path="/api/*",
-    summary="Swagger documentation",
-    description="Swagger documentation for this resource."
-)
-@HtmlDocConfig(
-    // Should override config annotations defined on class.
-    rank=10,
-    // Override the nav links for the swagger page.
-    navlinks={
-        "back: servlet:/",
-        "json: servlet:/?Accept=text/json&plainText=true"
-    },
-    // Never show aside contents of page inherited from class.
-    aside="NONE"
-)
-@MarshalledConfig(
-    // POJO swaps to apply to all serializers/parsers on this method.
-    swaps={
-        // Use the SwaggerUI swap when rendering Swagger beans.
-        // This is a per-media-type swap that only applies to text/html 
requests.
-        SwaggerUI.class
-    }
-)
-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);
-}
+@Rest(mixins = {BasicSwaggerUiResource.class, BasicRedocResource.class})
+public abstract class BasicRestServlet extends RestServlet implements ... { 
... }
 ```
 
-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.
+`BasicSwaggerUiResource` transitively pulls in `BasicSwaggerResource`, and 
`BasicRedocResource`
+transitively pulls in `BasicOpenApiResource`, so listing the two UI halves is 
enough to mount
+all six URLs. See [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) for the 
full composition
+reference (custom compositions, transitive resolution, parent-chain 
aggregation,
+`defaultAccept` content-negotiation behavior, format-pinned `/openapi.json` / 
`/openapi.yaml`).
 
-## `@Rest(apiFormat=…)` knob
+## Spec emission
 
-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).
+Both spec endpoints lean on the existing
+[`SwaggerProvider`](/site/apidocs/org/apache/juneau/rest/swagger/SwaggerProvider.html)
 and
+[`OpenApiProvider`](/site/apidocs/org/apache/juneau/rest/openapi/OpenApiProvider.html)
+infrastructure. The mixins do not implement their own emission paths; they 
expose what those
+providers already produce at the canonical URLs:
 
-| 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 |
+- `BasicSwaggerResource` returns the 
[`Swagger`](/site/apidocs/org/apache/juneau/bean/swagger/Swagger.html)
+  bean produced by the resource's `SwaggerProvider`. The class-level 
`@MarshalledConfig` applies
+  the 
[`SwaggerUI`](/site/apidocs/org/apache/juneau/dto/swagger/ui/SwaggerUI.html) 
swap when an
+  HTML response is being produced.
+- `BasicOpenApiResource` returns the 
[`OpenApi`](/site/apidocs/org/apache/juneau/bean/openapi3/OpenApi.html)
+  bean produced by the resource's `OpenApiProvider`. The class-level 
`@MarshalledConfig` applies
+  the 
[`RedocUI`](/site/apidocs/org/apache/juneau/dto/openapi3/ui/RedocUI.html) swap 
when an HTML
+  response is being produced.
 
-```java
-// Default behavior: Swagger v2 on /api/*; /openapi/* returns 404.
-@Rest
-public class LegacyResource extends BasicRestServlet { ... }
+Custom emission is supported by registering a custom `SwaggerProvider` / 
`OpenApiProvider`
+`@Bean` (Spring Boot) or via the bean-store registration path (microservice). 
See
+[API-Docs Mixin Pack § Spring Boot vs. 
microservice](/docs/topics/ApiDocsMixins#spring-boot-vs-microservice)
+for the resolution rules, including the `@Primary` precedence for 
multi-provider Spring Boot
+applications.
 
-// Modern resource: OpenAPI 3.1 on /openapi/*; /api/* returns 404.
-@Rest(apiFormat="openapi")
-public class ModernResource extends BasicRestServlet { ... }
+## Mounting on a non-`BasicRestServlet` resource
 
-// Both endpoints active for transitional rollouts.
-@Rest(apiFormat="both")
-public class TransitionResource extends BasicRestServlet { ... }
-```
+To get the same six-URL surface on a resource that does not extend 
`BasicRestServlet`, declare
+the mixin pack directly:
 
-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
+```java
+@Rest(path = "/api", mixins = {BasicSwaggerUiResource.class, 
BasicRedocResource.class})
+public class CustomApi extends RestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /api/items, /api/api, /api/swagger, /api/openapi, /api/openapi.json,
+//        /api/openapi.yaml, /api/redoc.
+```
 
-The OpenAPI 3.1 emission path collapses duplicated inline schemas into 
reusable `components.schemas` entries. Two layers contribute:
+To get only the OpenAPI 3.1 half (no Swagger v2, no `/api`, no `/swagger`):
 
-- **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 [...]
+```java
+@Rest(path = "/api", mixins = BasicRedocResource.class)
+public class CustomApi extends RestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /api/items, /api/openapi, /api/openapi.json, /api/openapi.yaml, 
/api/redoc.
+```
 
-## `?Swagger` / `?OpenApi` query mirrors on group resources
+To get the OpenAPI half on a `BasicRestServlet` subclass while dropping the 
inherited Swagger
+half, opt out of inheritance for the `mixins` property:
 
-`BasicGroupOperations` defines two `GET /` overloads gated by query parameter 
matchers so that group-style resources (`BasicRestServletGroup`, 
`BasicRestObjectGroup`, `BasicSpringRestServletGroup`) expose the API documents 
inline:
+```java
+@Rest(noInherit = {"mixins"}, mixins = BasicRedocResource.class)
+public class OpenApiOnly extends BasicRestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /items, /openapi, /openapi.json, /openapi.yaml, /redoc.
+```
 
-| 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`) |
+See [REST Server — Mixins and Multi-Mount Paths § Parent-chain mixin 
aggregation](/docs/topics/RestServerCompositionMixinsAndPaths#parent-chain-mixin-aggregation)
+for the aggregation rules that govern how `noInherit` and per-mixin 
declarations compose
+across an inheritance hierarchy.
 
-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).
+## `components.schemas` reuse
 
-```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 {}
-```
+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 `Schema<N>` name is 
assigned with
+  collision avoidance against existing entries.
+
+The dedup pass runs equally on the spec served by `/openapi`, `/openapi.json`, 
and
+`/openapi.yaml`, so all three deliver the same `components.schemas` shape.
+
+## See also
+
+- [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins) — the four-class 
composition reference:
+  default mount, custom compositions, transitive resolution, parent-chain 
aggregation,
+  `defaultAccept` behavior, format-pinned URLs, Spring Boot vs. microservice 
deployment, and
+  migration from `apiFormat`.
+- [REST Server — Mixins and Multi-Mount 
Paths](/docs/topics/RestServerCompositionMixinsAndPaths) —
+  the `@Rest(mixins=...)` and `@Rest(paths=...)` primitives, importer-wins 
precedence, and
+  parent-chain mixin aggregation rules.
+- [Swagger Basics](/docs/topics/SwaggerBasics) — Swagger v2 emission deeper 
dive: the
+  `Swagger` bean, `SwaggerUI` swap, `@HtmlDocConfig`-driven navlinks, and 
schema-lift
+  internals.
+- [V9.5 Migration Guide](/docs/topics/V95MigrationGuide) — full 
breaking-change table for
+  the api-docs surface, including `apiFormat`, `?Swagger` / `?OpenApi` query 
mirrors, and the
+  `BasicRestOperations` / `BasicGroupOperations` method removals.
diff --git a/pages/topics/10.16.02a.ApiDocsMixins.md 
b/pages/topics/10.16.02a.ApiDocsMixins.md
new file mode 100644
index 0000000000..71f783be1e
--- /dev/null
+++ b/pages/topics/10.16.02a.ApiDocsMixins.md
@@ -0,0 +1,282 @@
+---
+title: "API-Docs Mixin Pack"
+slug: ApiDocsMixins
+---
+
+The Juneau REST server ships with a four-class **api-docs mixin pack** in
+[`org.apache.juneau.rest.docs`](/site/apidocs/org/apache/juneau/rest/docs/package-summary.html)
+that drives every built-in Swagger / OpenAPI / Redoc endpoint on 
`BasicRestServlet` and
+`BasicRestObject`. Each class is a single-purpose `@Rest`-annotated resource 
designed to be
+grafted into your servlet via 
[`@Rest(mixins=...)`](/docs/topics/RestServerCompositionMixinsAndPaths)
+&mdash; pick the URLs you want, leave the ones you don't.
+
+> Migrating from a 9.4.x or earlier resource that used `@Rest(apiFormat="…")`? 
See the
+> [V9.5 Migration Guide](/docs/topics/V95MigrationGuide) for the removal table 
and worked
+> conversions. Short version: stop setting `apiFormat`, declare the mixins you 
want instead.
+
+## The four mixins at a glance
+
+| Mixin | Default `paths` | Content negotiation | Why it exists |
+|---|---|---|---|
+| 
[`BasicSwaggerResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicSwaggerResource.html)
 | `/api` | Standard Juneau content negotiation (HTML when `Accept: text/html`; 
serializer default otherwise). | Swagger v2 spec emission via the `Swagger` 
bean + `SwaggerUI` swap. |
+| 
[`BasicSwaggerUiResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicSwaggerUiResource.html)
 | `/swagger` | Bare browser GET (no `Accept`) defaults to `text/html`; 
explicit `Accept` is honored normally. | Same Swagger v2 spec mounted at an 
HTML-first URL. Declares `@Rest(mixins=BasicSwaggerResource.class)` so 
transitive resolution brings `/api` along for free. |
+| 
[`BasicOpenApiResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicOpenApiResource.html)
 | `/openapi`, `/openapi.json`, `/openapi.yaml` | `/openapi` uses standard 
Juneau content negotiation; `/openapi.json` and `/openapi.yaml` are 
**format-pinned** (ignore `Accept`). | OpenAPI 3.1 spec emission via the 
`OpenApi` bean + `RedocUI` swap. |
+| 
[`BasicRedocResource`](/site/apidocs/org/apache/juneau/rest/docs/BasicRedocResource.html)
 | `/redoc` | Bare browser GET (no `Accept`) defaults to `text/html`; explicit 
`Accept` is honored normally. | Same OpenAPI 3.1 spec mounted at an HTML-first 
URL. Declares `@Rest(mixins=BasicOpenApiResource.class)` so transitive 
resolution brings `/openapi/*` along for free. |
+
+The split is intentional: **spec mixins** (`BasicSwaggerResource`, 
`BasicOpenApiResource`) emit
+the document and let the standard content negotiation pipeline decide the wire 
format. **UI
+mixins** (`BasicSwaggerUiResource`, `BasicRedocResource`) tweak content 
negotiation so a bare
+browser GET — which sends `Accept: */*` or no `Accept` at all — renders the 
HTML view by
+default, while machine clients that send `Accept: application/json` (or 
`application/yaml`)
+still get the spec in their requested format. This keeps the same `/swagger` 
URL useful for
+both "open in browser" and "fetch as JSON" without query-string toggles.
+
+## Default mount on `BasicRestServlet` / `BasicRestObject`
+
+Any subclass of 
[`BasicRestServlet`](/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServlet.html)
+or 
[`BasicRestObject`](/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObject.html)
 gets the
+full pack by default. Both classes declare:
+
+```java
+@Rest(mixins = {BasicSwaggerUiResource.class, BasicRedocResource.class})
+public abstract class BasicRestServlet extends RestServlet implements ... { 
... }
+```
+
+Because `BasicSwaggerUiResource` transitively pulls in `BasicSwaggerResource` 
and
+`BasicRedocResource` transitively pulls in `BasicOpenApiResource`, the mixin 
walk dedupes by
+class identity and the resulting servlet serves **six URLs**:
+
+| URL | Mixin | Wire format |
+|---|---|---|
+| `GET /api` | `BasicSwaggerResource` | Swagger v2 spec; content-negotiated. |
+| `GET /swagger` | `BasicSwaggerUiResource` | Swagger UI (HTML default; JSON / 
XML on explicit `Accept`). |
+| `GET /openapi` | `BasicOpenApiResource` | OpenAPI 3.1 spec; 
content-negotiated. |
+| `GET /openapi.json` | `BasicOpenApiResource` | OpenAPI 3.1 spec; **always 
JSON**. |
+| `GET /openapi.yaml` | `BasicOpenApiResource` | OpenAPI 3.1 spec; **always 
YAML**. |
+| `GET /redoc` | `BasicRedocResource` | Redoc UI (HTML default; JSON / XML on 
explicit `Accept`). |
+
+Subclasses that just want this surface need to declare nothing — extending
+`BasicRestServlet` is enough.
+
+## Composing your own surface
+
+The mixins are independent — pick the URLs you want. The four common 
compositions:
+
+### Just the OpenAPI spec (no Swagger v2, no UI)
+
+```java
+@Rest(path = "/api", mixins = BasicOpenApiResource.class)
+public class ApiResource extends RestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /api/items, /api/openapi, /api/openapi.json, /api/openapi.yaml.
+```
+
+### OpenAPI spec plus the Redoc UI
+
+```java
+@Rest(path = "/api", mixins = BasicRedocResource.class)   // pulls 
BasicOpenApiResource in transitively
+public class ApiResource extends RestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /api/items, /api/openapi, /api/openapi.json, /api/openapi.yaml, 
/api/redoc.
+```
+
+### Full surface (mirrors `BasicRestServlet`, but on a plain `RestServlet`)
+
+```java
+@Rest(path = "/api", mixins = {BasicSwaggerUiResource.class, 
BasicRedocResource.class})
+public class ApiResource extends RestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /api/items, /api/api, /api/swagger, /api/openapi, /api/openapi.json,
+//        /api/openapi.yaml, /api/redoc.
+```
+
+### Drop one half of the inherited pack
+
+`BasicRestServlet` already mixes in both UI halves. To keep only the OpenAPI 
side on a
+subclass, opt out of inherited mixins and re-declare the mixin you want:
+
+```java
+@Rest(noInherit = {"mixins"}, mixins = BasicRedocResource.class)
+public class MyApi extends BasicRestServlet {
+    @RestGet("/items") public List<Item> items() { ... }
+}
+// Serves /items, /openapi, /openapi.json, /openapi.yaml, /redoc — but NOT 
/api or /swagger.
+```
+
+### Standalone mount via `@Rest(paths=...)`
+
+Each mixin is itself a fully-fledged `@Rest`-annotated resource and can be 
subclassed and
+mounted as its own top-level servlet:
+
+```java
+@Rest(paths = {"/openapi", "/openapi.json", "/openapi.yaml"})
+public class OpenApiDocs extends BasicOpenApiResource { }
+```
+
+Both deployment styles (mixin into an existing servlet vs. mount as a sibling 
servlet) work the
+same way under Spring Boot and under the Jetty microservice — see
+[Spring Boot vs. microservice](#spring-boot-vs-microservice) below.
+
+## No-`Accept`-defaults-to-HTML on UI mixins
+
+The two UI mixins both declare `@Rest(defaultAccept = "text/html")`:
+
+```java
+@Rest(paths = {"/swagger"}, mixins = BasicSwaggerResource.class, defaultAccept 
= "text/html")
+public class BasicSwaggerUiResource implements BasicUniversalConfig { ... }
+
+@Rest(paths = {"/redoc"}, mixins = BasicOpenApiResource.class, defaultAccept = 
"text/html")
+public class BasicRedocResource implements BasicUniversalConfig { ... }
+```
+
+`defaultAccept` only fires when the request has **no** `Accept` header (or 
sends `*/*`,
+which collapses to "no preference"). When that happens, the framework injects
+`Accept: text/html` before content negotiation runs, and the `SwaggerUI` / 
`RedocUI` swap
+fires to render the HTML view.
+
+That gives the natural behaviors:
+
+| Request | Result on `/swagger` and `/redoc` |
+|---|---|
+| Bare browser GET (no `Accept`) | HTML view via the UI swap. |
+| `Accept: application/json` | Spec serialized as JSON via the standard 
serializer. |
+| `Accept: application/xml` | Spec serialized as XML. |
+| `Accept: */*` | HTML view (same as bare browser). |
+
+The spec mixins (`/api`, `/openapi`) do **not** declare `defaultAccept`, so 
the standard
+content-negotiation default (the serializer's preferred media type, typically 
JSON) wins when
+no `Accept` header is sent.
+
+## Format-pinned `/openapi.json` and `/openapi.yaml`
+
+`BasicOpenApiResource` declares three `@RestGet` methods, one per mount path:
+
+```java
+@RestGet("/*")                                              // 
content-negotiated
+public OpenApi getOpenApi(RestRequest req) { ... }
+
+@RestGet(path = "/openapi.json", produces = "application/json")
+public void getOpenApiJson(RestRequest req, RestResponse res) throws Exception 
{
+    
res.getDirectWriter("application/json").write(req.getOpenApi().orElseThrow(NotFound::new).toJson());
+}
+
+@RestGet(path = "/openapi.yaml", produces = "application/yaml")
+public void getOpenApiYaml(RestRequest req, RestResponse res) throws Exception 
{
+    
res.getDirectWriter("application/yaml").write(req.getOpenApi().orElseThrow(NotFound::new).toYaml());
+}
+```
+
+The two `.json` and `.yaml` endpoints use
+[`RestResponse.getDirectWriter(...)`](/site/apidocs/org/apache/juneau/rest/RestResponse.html)
+to write the spec directly to the response body in the requested wire format, 
bypassing the
+standard content-negotiation pipeline. That means:
+
+- `GET /openapi.json` returns JSON regardless of `Accept`. Even `Accept: 
text/html` returns
+  JSON. The path implies the format.
+- `GET /openapi.yaml` returns YAML regardless of `Accept`.
+- `GET /openapi` (no suffix) runs the normal pipeline — `Accept: 
application/json` gets JSON,
+  `Accept: application/yaml` gets YAML, `Accept: text/html` gets the Redoc UI 
swap (via the
+  `BasicRedocResource` mixin if it's in the pack; otherwise the serializer 
default).
+
+This split lets API docs consumers use direct, unambiguous URLs for the spec 
(`/openapi.json`,
+`/openapi.yaml`) without trusting the server's content negotiation, while 
keeping `/openapi`
+useful for browsers.
+
+## Spring Boot vs. microservice
+
+The mixin pack works identically under both deployment paths because mixin 
instances are
+resolved through the importing servlet's 
[`BeanStore`](/site/apidocs/org/apache/juneau/commons/inject/BeanStore.html):
+
+- **Microservice path.** `BasicBeanStore` looks up each mixin class via
+  `getBean(<MixinClass>.class)` first; if no bean is registered, the framework 
reflects a
+  no-arg constructor on the mixin. Verified end-to-end by
+  `BasicApiDocs_JettyMicroservice_Test` in `juneau-utest`, which boots a real 
Jetty
+  `Microservice` on an ephemeral port and hits all six URLs over HTTP.
+- **Spring Boot path.**
+  
[`SpringBeanStore`](/site/apidocs/org/apache/juneau/rest/springboot/SpringBeanStore.html)
+  extends `BasicBeanStore` and delegates unresolved lookups to
+  `ApplicationContext.getBeanProvider(beanType).getIfAvailable()`. Same 
fallback to no-arg
+  constructor if no Spring bean is registered. Verified end-to-end by
+  `BasicApiDocs_Springboot_Test` in `juneau-utest`, which boots a full Spring 
Boot context
+  with embedded Tomcat and hits the same six URLs.
+
+Both tests share a fixture (`BasicApiDocsTestFixtures`) so the response-shape 
assertions are
+identical across deployment paths. Drift in either direction fails CI.
+
+### Multiple `OpenApiProvider` beans in Spring Boot
+
+If your Spring Boot app registers more than one
+[`OpenApiProvider`](/site/apidocs/org/apache/juneau/rest/openapi/OpenApiProvider.html)
 `@Bean`
+(rare, but possible when you want a default and a custom provider 
co-existing), mark exactly
+one of them `@Primary`:
+
+```java
+@Configuration
+public class OpenApiConfig {
+
+    @Bean
+    @Primary
+    public OpenApiProvider productionProvider() {
+        return new MyCustomOpenApiProvider(...);
+    }
+
+    @Bean
+    public OpenApiProvider fallbackProvider() {
+        return new BasicOpenApiProvider(...);
+    }
+}
+```
+
+`SpringBeanStore.getBean(OpenApiProvider.class)` delegates to
+`ApplicationContext.getBeanProvider(OpenApiProvider.class).getIfAvailable()`, 
which honors
+`@Primary` correctly. The `BasicOpenApiResource` mixin sees the `@Primary` 
bean.
+
+If you register two `OpenApiProvider` beans **without** marking one 
`@Primary`, Spring fails
+the context-load with
+`BeanDefinitionOverrideException` (when two `@Bean` methods declare the same 
bean id) or
+falls through to `getIfUnique()` returning `null` (when two `@Bean` methods 
declare different
+bean ids and neither is primary). Both failure modes happen **before** Juneau 
gets a chance
+to look the provider up, so Juneau cannot give a friendlier error here. This 
is a documented
+"known rough edge"; the fix is always to mark one bean `@Primary`. Both the 
success and the
+collision cases are pinned by 
`BasicApiDocs_Springboot_MultiOpenApiProvider_Test`.
+
+The same precedence rule applies to
+[`SwaggerProvider`](/site/apidocs/org/apache/juneau/rest/swagger/SwaggerProvider.html)
 for the
+Swagger v2 side: multiple beans require exactly one `@Primary` marking.
+
+## Migrating from `apiFormat`
+
+The 9.4.x `@Rest(apiFormat = "swagger" | "openapi" | "both")` annotation 
member, the
+`juneau.rest.apiFormat` system property, the `RestContext.getApiFormat()` 
accessor, and the
+`?Swagger` / `?OpenApi` query mirrors on group resources are all **removed in 
9.5.0**.
+Compose the mixins you want instead. The
+[V9.5 Migration Guide](/docs/topics/V95MigrationGuide) has the full removal 
table; the
+short version:
+
+| Old (9.4.x) | New (9.5.0) |
+|---|---|
+| `@Rest(apiFormat = "swagger")` | Default behavior of `BasicRestServlet` — no 
change needed. |
+| `@Rest(apiFormat = "openapi")` | `@Rest(noInherit = {"mixins"}, mixins = 
BasicRedocResource.class)` on a `BasicRestServlet` subclass. |
+| `@Rest(apiFormat = "both")` | Default behavior of `BasicRestServlet` — both 
halves are always mounted. |
+| `GET /api?Swagger=…` | `GET /api` (or `GET /swagger` for the HTML view). |
+| `GET /?OpenApi=…` | `GET /openapi` (or `GET /redoc` for the HTML view). |
+| `juneau.rest.apiFormat` system property | Removed. Pick mixins at compile 
time. |
+
+## See also
+
+- [REST Server — Mixins and Multi-Mount 
Paths](/docs/topics/RestServerCompositionMixinsAndPaths) —
+  the `@Rest(mixins=...)` and `@Rest(paths=...)` primitives this pack builds 
on, including the
+  parent-chain mixin aggregation rules that govern how mixins compose across 
an inheritance
+  hierarchy.
+- [Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts) — per-mixin 
`RestContext`
+  inheritance model; each api-docs mixin has its own sub-context so the 
`RestResponse`,
+  `defaultAccept`, and `produces` settings only affect that mixin's endpoints.
+- [Basic REST Servlet Swagger](/docs/topics/BasicRestServletSwagger) — 
pre-mixin background
+  on Swagger / OpenAPI emission and `components.schemas` reuse.
+- [V9.5 Migration Guide](/docs/topics/V95MigrationGuide) — full 
breaking-change table and
+  worked migration steps for 9.4.x → 9.5.0.
+- [OpenAPI 3.1 Server Emission](/docs/topics/RestServerOpenApi) — the 
underlying spec-emission
+  pipeline that `BasicOpenApiResource` exposes.
diff --git a/pages/topics/23.01.V9.5-migration-guide.md 
b/pages/topics/23.01.V9.5-migration-guide.md
index 19d965a620..5e72b7b481 100644
--- a/pages/topics/23.01.V9.5-migration-guide.md
+++ b/pages/topics/23.01.V9.5-migration-guide.md
@@ -673,17 +673,18 @@ The previous `juneau-all` Maven module has been 
**removed** in favor of `juneau-
 |-----|-----|-------|
 | `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 + API-docs Mixin Pack (TODO-63 + TODO-74)
 
-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).
+OpenAPI 3.1 emission is new in 9.5; existing Swagger v2 emission is unchanged. 
Composition is done by listing api-docs mixins in `@Rest(mixins=...)` rather 
than via a string knob — the `apiFormat` annotation member, system property, 
and `RestContext.getApiFormat()` accessor that briefly existed during the 9.5 
development cycle were removed before release in favor of compose-by-class.
 
 | 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 [...]
+| Resource exposed only Swagger v2 at `/api/*`. No way to opt into OpenAPI 3.1 
emission. | Apply one or more api-docs mixins from 
`org.apache.juneau.rest.docs`: `BasicSwaggerResource` (`/api`), 
`BasicSwaggerUiResource` (`/swagger`), `BasicOpenApiResource` (`/openapi`, 
`/openapi.json`, `/openapi.yaml`), `BasicRedocResource` (`/redoc`). Subclasses 
of `BasicRestServlet` / `BasicRestObject` get all four (composed as 
`@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})`) au 
[...]
+| `BasicRestOperations.getSwagger(RestRequest)` and `getOpenApi(RestRequest)` 
interface methods | Removed. The endpoints are now mounted via the api-docs 
mixin pack on `BasicRestServlet` / `BasicRestObject`. User-written 
`BasicRestOperations` implementers should remove their old `getSwagger` / 
`getOpenApi` overrides — the methods are no longer on the interface. | If you 
need a different api-docs surface, add `@Rest(noInherit={"mixins"}, 
mixins=...)` on your subclass. |
+| `BasicGroupOperations.getChildrenSwagger(RestRequest)` / 
`getChildrenOpenApi(RestRequest)` (the `?Swagger` / `?OpenApi` query mirrors on 
group resources) and the `HasSwaggerQueryParam` / `HasOpenApiQueryParam` 
matcher inner classes | Removed. Hit `/api`, `/swagger`, `/openapi`, or 
`/redoc` directly — they are always mounted on `BasicRestServletGroup` / 
`BasicRestObjectGroup` via the api-docs mixin pack. | External docs that linked 
to `/?Swagger=true` need updating to `/api` (or `/swagger`). |
 | `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)`).
+Reference: see the per-module section in the 9.5 release notes (`### 
juneau-rest-server` → `OpenAPI 3.1 Emission + API-docs Mixin Pack (TODO-63 + 
TODO-74)`).
 
 <!-- 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 bf7dd96d43..37d75e415e 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1387,16 +1387,21 @@ const sidebars: SidebarsConfig = {
                                                                        id: 
'topics/10.16.01.SwaggerBasics',
                                                                        label: 
'10.16.1. Swagger Basics',
                                                                },
-                                                               {
-                                                                       type: 
'doc',
-                                                                       id: 
'topics/10.16.02.BasicRestServletSwagger',
-                                                                       label: 
'10.16.2. Basic REST Servlet Swagger',
-                                                               },
-                                                               {
-                                                                       type: 
'doc',
-                                                                       id: 
'topics/10.16.03.BasicSwaggerInfo',
-                                                                       label: 
'10.16.3. Basic Swagger Info',
-                                                               },
+                                                       {
+                                                               type: 'doc',
+                                                               id: 
'topics/10.16.02.BasicRestServletSwagger',
+                                                               label: 
'10.16.2. Basic REST Servlet Swagger',
+                                                       },
+                                                       {
+                                                               type: 'doc',
+                                                               id: 
'topics/10.16.02a.ApiDocsMixins',
+                                                               label: 
'10.16.2a. API-Docs Mixin Pack',
+                                                       },
+                                                       {
+                                                               type: 'doc',
+                                                               id: 
'topics/10.16.03.BasicSwaggerInfo',
+                                                               label: 
'10.16.3. Basic Swagger Info',
+                                                       },
                                                                {
                                                                        type: 
'doc',
                                                                        id: 
'topics/10.16.04.SwaggerTags',

Reply via email to