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 7388618b64 docs: dynamic child REST resources + inject-aware
microservice + next-gen REST client topics
7388618b64 is described below
commit 7388618b642074d1a08f3a05332d2f1ce038cc3c
Author: James Bognar <[email protected]>
AuthorDate: Fri May 15 10:44:59 2026 -0400
docs: dynamic child REST resources + inject-aware microservice + next-gen
REST client topics
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 189 ++++++++++++++++++++
pages/topics/10.03.03.ChildResources.md | 65 +++++++
pages/topics/12.15.NextGenRestClient.md | 237 ++++++++++++++++++++++++++
pages/topics/14.09.InjectAwareMicroservice.md | 184 ++++++++++++++++++++
sidebars.ts | 10 ++
5 files changed, 685 insertions(+)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index d33162ed5d..154dbf38d6 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -1514,6 +1514,29 @@ String name
### juneau-rest-server
+#### 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=…)`.
+
+```java
+@Rest(path="/root")
+public class MyServer extends BasicRestServletGroup {}
+
+var server = new MyServer();
+// ... bootstrap into Jetty / Spring Boot / MockRestClient ...
+
+server.addChild(AlphaChild.class); // by class, path from @Rest(path)
+server.addChild(new BetaChild()); // by instance, path from
@Rest(path)
+server.addChild("/gamma", new AlphaChild()); // explicit path override
+server.removeChild("/alpha");
+server.removeChild(AlphaChild.class);
+var keys = server.getChildResources().asMap().keySet();
+```
+
+The underlying state lives on `RestChildren`, which now holds children in a
`volatile` copy-on-write snapshot. Route matching reads the snapshot without
locks (preserving hot-path performance), and mutations are serialized through
an internal write lock. `@RestInit` / `@RestPostInit` /
`@RestPostInitChildFirst` run before `addChild` returns; `@RestDestroy` and
`Servlet.destroy()` run on `removeChild` (recursively for grandchildren).
Adding to an occupied path without `replace=true` throw [...]
+
+See [Child Resources](/docs/topics/ChildResources#dynamic-child-resources) for
the full API and usage notes.
+
#### `@RestInject` Renamed to `@Bean` (moved to `juneau-commons`)
`@RestInject` has been renamed to `@Bean` and moved from
`org.apache.juneau.rest.annotation` to
@@ -1743,6 +1766,172 @@ If you previously relied on `@Bean` (inject) overriding
a Spring `@Bean`, you ha
- **RestClient `rootUrl`**: The `rootUrl` field is now stored as a
`Supplier<String>` internally. Code that relies on reflection to access the
private `rootUrl` field directly (unusual but possible) will now see a
`Supplier<String>` instead of a `String`.
+### juneau-microservice-core
+
+#### Inject-Aware Microservice (TODO-31)
+
+`Microservice` is now inject-aware. Every microservice owns an internal
`WritableBeanStore`
+(from `juneau-commons`) populated from `@Configuration` classes registered on
the builder.
+
+**New builder methods**:
+
+- `Microservice.Builder.configurations(Class<?>...)` — register one or more
`@Configuration` classes
+ whose `@Bean` methods/fields are processed at bootstrap.
+- `Microservice.Builder.configurations(List<Class<?>>)` — same, list form.
+- `Microservice.Builder.beanStore(WritableBeanStore)` — supply an
externally-owned store instead of
+ letting the microservice construct a fresh one. Useful for composing into a
larger application or
+ integrating with `juneau-rest-server-springboot`.
+
+**New accessor**:
+
+- `Microservice.getBeanStore()` — never null. Contains the resolved `Args`,
`ManifestFile`, `Config`,
+ `VarResolver`, `MicroserviceListener`, the `Microservice` instance itself,
plus everything
+ contributed by registered `@Configuration` classes.
+
+**Lifecycle**:
+
+- `@PostConstruct` callbacks fire on beans instantiated through the inject
framework (e.g. via
+ constructor injection in `@Bean` factory methods or
`BeanStore.instantiate(X)`).
+- `@PreDestroy` callbacks fire on `Microservice.stop()`, which now closes the
bean store and walks
+ every resolved bean in LIFO order. Errors are logged at `WARNING` and do not
abort the rest of
+ the shutdown sequence.
+
+**Resolution priority** for core types (`Args`, `Config`, etc.): explicit
builder call wins, then
+`@Bean`-supplied value, then built-in default. The final resolved value is
registered into the bean
+store so downstream `@Bean` factory methods see the same instance that the
`Microservice.getXxx()`
+getter returns.
+
+This change is **fully additive** — every existing builder method continues to
work unchanged, and
+microservices that never call `.configurations(...)` behave identically to
pre-9.5.0.
+
+See <a href="/docs/topics/MicroserviceCoreInject">Inject-Aware
Microservice</a> for the full guide.
+
+### juneau-microservice-jetty
+
+#### Auto-Discovery of `@Rest` Servlets (TODO-31)
+
+`JettyMicroservice.createServer()` now consults the bean store for `Servlet`
beans contributed via
+`@Configuration` classes. Any servlet whose runtime class is annotated with
`@Rest` is automatically
+mounted at the path declared by `@Rest(path = "...")` (or `/` when no path is
set).
+
+**`@Bean`-supplied infrastructure** — when no builder value is set, these
types are resolved from
+the bean store:
+
+- `JettyServerFactory` — replaces `BasicJettyServerFactory` if a custom
factory is contributed.
+- `JettyMicroserviceListener` — replaces the default no-op listener.
+- `org.eclipse.jetty.server.Server` — when present (with
`"ServletContextHandler"` attribute set),
+ `createServer()` uses it directly and skips the `jetty.xml` factory step
entirely. Useful for
+ tests and fully-code-driven Jetty setups.
+
+**Path-collision enforcement** — `createServer()` now tracks each servlet
pathspec with its
+declaring source and throws a `RuntimeException` with both contributor names
when two servlets claim
+the same path. This covers all sources: `Jetty/servlets`, `Jetty/servletMap`,
`.servlet(...)` builder
+calls, and `@Bean`-discovered servlets. Previously silent collisions are now
hard failures at
+startup.
+
+> **Note on `@Bean` method return types** — declare `@Bean` methods that
produce a servlet with
+> return type `Servlet` (not the concrete subclass). Juneau's
`BeanStore.getBeansOfType` is
+> exact-type, so a bean registered under `MyRestServlet.class` would not be
visible to the
+> auto-mount logic which queries for `Servlet.class`.
+
+### Next-Generation REST Client and HTTP Stack (Beta)
+
+Juneau 9.5.0 introduces a new REST client and HTTP type stack under
`org.apache.juneau.ng.*` that decouples Juneau from Apache HttpClient 4.5. The
new stack is shipped **alongside** the existing `juneau-rest-client` /
`juneau-rest-common` APIs — both continue to work unchanged.
+
+**Beta API**: All public types under `org.apache.juneau.ng.*` are beta and may
change incompatibly in the next major release. For production deployments that
require strict binary stability, continue using the classic `RestClient` /
`juneau-rest-common` APIs until the NG stack is declared stable.
+
+#### `org.apache.juneau.ng.http` (in `juneau-rest-common`)
+
+A JDK-native replacement for the Apache HttpCore-based types in
`juneau-rest-common`. No `httpcore` dependency in this package — all interfaces
(`HttpHeader`, `HttpPart`, `HttpBody`, `HttpStatusLine`, `HttpResponseMessage`)
use JDK types only.
+
+- Typed header hierarchy (`HttpStringHeader`, `HttpMediaTypeHeader`,
`HttpMediaRangesHeader`, `HttpStringRangesHeader`, `HttpCsvHeader`,
`HttpDateHeader`, `HttpUriHeader`, `HttpIntegerHeader`, `HttpLongHeader`,
`HttpBooleanHeader`, `HttpEntityTagHeader`, `HttpEntityTagsHeader`) with ~73
RFC-named header classes (`Accept`, `ContentType`, `Authorization`, …).
+- Entity / body types: `HttpBodyBean`, `StringBody`, `ByteArrayBody`,
`StreamBody`, `FileBody`, `MultipartBody` (RFC 7578 streaming).
+- Status / response types: `HttpStatusLineBean`, `BasicHttpResponse`,
`BasicHttpException`, ~50 named status classes (`Ok`, `Created`, `NotFound`,
`InternalServerError`, …).
+- Remote-proxy annotations re-homed under `org.apache.juneau.ng.http.remote`
(`@Remote`, `@RemoteGet`, `@RemotePost`, `@RemotePut`, `@RemotePatch`,
`@RemoteDelete`, `@RemoteOp`, `@RemoteReturn`).
+
+#### `org.apache.juneau.ng.rest.client` (in `juneau-rest-client`)
+
+The transport abstraction and the new `NgRestClient` / `NgRestRequest` /
`NgRestResponse`.
+
+- `HttpTransport` — single integration point replacing
`org.apache.http.client.HttpClient`. Provides synchronous
`send(TransportRequest)` and a `sendAsync(...)` default that transports can
override for native async.
+- `TransportRequest`, `TransportResponse` (`Closeable` — owns
connection-release hook), `TransportHeader`, `TransportBody`,
`TransportException` — transport-layer DTOs using JDK types only.
+- `HttpTransportBuilder` / `HttpTransportProvider` SPI — `ServiceLoader`-based
auto-discovery; passing a fully-built transport or transport builder is also
supported.
+- `NgRestClient` — composes an `HttpTransport`; one
serializer/parser/marshaller per client (no multi-language mode); fluent
`get/post/put/patch/delete/head/options/formPost/multipartPost` API; remote
proxies via `getRemote(Class)`; explicit `shutdown()` (not `Closeable`).
+- `NgRestRequest` — the single `Closeable` in the user-facing API; resolves
URI + path data + query, runs interceptors, serializes the body, calls the
transport.
+- `CollectionFormat` enum (`COMMA`, `PIPE`, `SPACE`, `TAB`, `REPEATED`) for
`Iterable` / array-valued params.
+- `BodyConverter` — pluggable request-body conversion; default chain handles
`HttpBody`, `MultipartBody`, `InputStream`, `byte[]`, `Reader`, `File`,
`PartList`, and falls back to a `SerializedBody` that streams through the
client's serializer.
+- `RestCallInterceptor` — `onInit` / `onConnect` / `onClose` lifecycle hooks
(no Apache types).
+- `RestLogger` / `RestLogEntry` / `RestLogLevelResolver` / `BasicRestLogger` —
framework-agnostic logging via `java.lang.System.Logger`, with named-template
formatting (`{method}`, `{uri}`, `{status}`, `{reason}`, `{elapsed}`,
`{req.headers}`, `{req.body}`, `{res.headers}`, `{res.body}`, `{error}`) and
configurable per-level templates.
+
+#### Transport Implementations (new modules)
+
+Each new module is independent and pulls in only its own native client. The
`HttpTransportProvider` `ServiceLoader` entries let
`NgRestClient.create().build()` auto-discover the highest-priority transport on
the classpath.
+
+| Module | Artifact ID | Native client |
+|---|---|---|
+| Apache HttpClient 4.5 | `juneau-ng-rest-client-apache-httpclient-45` |
`org.apache.httpcomponents:httpclient:4.5.x` |
+| Apache HttpClient 5 | `juneau-ng-rest-client-apache-httpclient-50` |
`org.apache.httpcomponents.client5:httpclient5` |
+| JDK `HttpClient` | `juneau-ng-rest-client-java-httpclient` |
`java.net.http.HttpClient` (Java 11+) — zero third-party deps |
+| OkHttp | `juneau-ng-rest-client-okhttp` | `com.squareup.okhttp3:okhttp` |
+| Eclipse Jetty client | `juneau-ng-rest-client-jetty` |
`org.eclipse.jetty:jetty-client` |
+
+Each transport module ships `*Transport`, `*TransportBuilder` (with native
client–specific configuration accessible via cast), and `*TransportProvider`.
Common transport-builder configuration (`connectTimeout`, `readTimeout`,
`sslContext`, `hostnameVerifier`, `proxy`, `maxConnections`,
`maxConnectionsPerRoute`, `followRedirects`, …) lives on `HttpTransportBuilder`
so most callers don't need to cast.
+
+#### `juneau-rest-mock` — `org.apache.juneau.ng.rest.mock`
+
+- `MockHttpTransport` — routes `TransportRequest` directly to a Juneau
`RestContext` without network I/O.
+- `NgMockRestClient` — extends `NgRestClient` and wires it to
`MockHttpTransport`. Replaces the classic
`MockRestClient`-implements-`HttpClientConnection` shim with a plain
`HttpTransport` implementation.
+
+#### Usage Examples
+
+```java
+// Pre-built serializer/parser — configure marshalling externally
+var serializer = Json5Serializer.create().build();
+var parser = Json5Parser.create().build();
+
+// Pick a transport explicitly
+NgRestClient client = NgRestClient.create()
+ .transportBuilder(ApacheHc45Transport.create()
+ .connectTimeout(Duration.ofSeconds(10))
+ .sslContext(mySSLContext)
+ .maxConnections(50))
+ .serializer(serializer)
+ .parser(parser)
+ .rootUrl("https://api.example.com")
+ .build();
+
+// Or let the SPI auto-discover the best transport on the classpath
+NgRestClient zeroConfig = NgRestClient.create()
+ .marshaller(Json5.DEFAULT)
+ .rootUrl("https://api.example.com")
+ .build();
+
+// Always use try-with-resources on the request — close releases the connection
+try (var req = client.get("/users/{id}").pathData("id", 42)) {
+ var user = req.run().as(User.class);
+}
+
+// Multipart upload
+try (var req = client.multipartPost("/upload")
+ .multipartField("album", "vacation")
+ .multipartFile("photo", Path.of("/tmp/beach.jpg"), "image/jpeg")) {
+ req.run();
+}
+
+// At application shutdown
+client.shutdown();
+```
+
+#### Migration Path
+
+The classic `RestClient` / `juneau-rest-common` APIs remain fully supported
with no behavior change. Migration is **opt-in**:
+
+1. Add the NG transport module for your preferred HTTP stack to your build.
+2. Replace `RestClient.create()` with `NgRestClient.create()` and adapt
builder calls — most configuration (`rootUrl`, default headers/query/form/path,
interceptors, marshaller) maps 1:1.
+3. Use try-with-resources around `NgRestRequest`; call `shutdown()` on the
client at application shutdown.
+
+The classic stack is **not** deprecated in 9.5; the NG stack will graduate to
stable in a future release, at which point the classic stack may be deprecated
and eventually removed.
+
### juneau-bean-mcp (new module)
A new bean module, `juneau-bean-mcp`, models the [Model Context
Protocol](https://modelcontextprotocol.io/) wire format as Juneau `@Bean`
POJOs. The beans round-trip cleanly through any Juneau serializer/parser (JSON,
JSON5, MessagePack, CBOR, YAML, etc.) and were the foundation for
`juneau-rest-server-mcp`.
diff --git a/pages/topics/10.03.03.ChildResources.md
b/pages/topics/10.03.03.ChildResources.md
index 87a7c67646..e4d60daff7 100644
--- a/pages/topics/10.03.03.ChildResources.md
+++ b/pages/topics/10.03.03.ChildResources.md
@@ -37,3 +37,68 @@ Children can be nested arbitrary deep to create complex REST
interfaces with a s
As explained earlier, child REST objects typically extend from <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObject.html"
target="_blank">BasicRestObject</a> or <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObjectGroup.html"
target="_blank">BasicRestObjectGroup</a> and not from one of the servlet
classes.
They also technically don't even need to extend from those classes and can
instead just be a normal class annotated with
the bare-minimum <a
href="/site/apidocs/org/apache/juneau/rest/annotation/Rest.html"
target="_blank">@Rest</a> and <a
href="/site/apidocs/org/apache/juneau/rest/annotation/RestOp.html"
target="_blank">@RestOp</a> annotations.
+
+## Dynamic Child Resources
+
+In addition to declaring children statically via `@Rest(children=…)`, parent
resources that extend
+<a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestServletGroup.html"
target="_blank">BasicRestServletGroup</a>
+or <a
href="/site/apidocs/org/apache/juneau/rest/servlet/BasicRestObjectGroup.html"
target="_blank">BasicRestObjectGroup</a>
+can add and remove child resources at runtime.
+This is useful for plug-in architectures, integration tests, and other
scenarios where the set of mounted endpoints is not known ahead of time.
+
+The convenience methods on the group classes delegate to <a
href="/site/apidocs/org/apache/juneau/rest/RestChildren.html"
target="_blank">RestChildren</a>,
+which holds children in a thread-safe, copy-on-write snapshot — reads on the
request hot path are lock-free, while mutations are serialized through a write
lock.
+
+:::tip Example
+```java
+@Rest(path="/root")
+public class MyServer extends BasicRestServletGroup {}
+
+@Rest(path="/alpha")
+public class AlphaChild extends BasicRestObject {
+ @RestGet("/ping")
+ public String ping() { return "alpha-pong"; }
+}
+
+var server = new MyServer();
+// ... bootstrap into Jetty / Spring Boot / MockRestClient ...
+
+// Add by class — path is derived from the child's @Rest(path) annotation.
+server.addChild(AlphaChild.class);
+
+// Add by instance — useful when the child is hand-constructed or comes from a
bean store.
+var beta = new BetaChild();
+server.addChild(beta);
+
+// Add with an explicit path override.
+server.addChild("/gamma", new AlphaChild());
+
+// Remove by path or by class.
+server.removeChild("/alpha");
+server.removeChild(AlphaChild.class);
+
+// Inspect what is currently mounted.
+var keys = server.getChildResources().asMap().keySet();
+```
+:::
+
+### API surface
+
+| Method | Description |
+|---|---|
+| `addChild(Class<?>)` | Instantiate from the bean store (or via no-arg
constructor) and mount at the child's `@Rest(path)`. |
+| `addChild(Object)` | Mount an existing instance at the child's
`@Rest(path)`. |
+| `addChild(Object, boolean replace)` | Same as above but, when
`replace=true`, atomically destroys any existing child mounted at the same
path. |
+| `addChild(String path, Object)` | Mount an existing instance at an explicit
path, ignoring any `@Rest(path)` on the resource. |
+| `addChild(String path, Object, boolean replace)` | Explicit-path variant
with replace semantics. |
+| `removeChild(String path)` | Remove the child mounted at the given path.
Returns the removed `RestContext` (or `null`). |
+| `removeChild(Class<?>)` | Remove the child whose resource is an instance of
the given class. Returns the removed `RestContext` (or `null`). |
+| `getChildResources()` | Returns the underlying `RestChildren` for direct
inspection. |
+
+### Lifecycle and concurrency
+
+When a child is added, its `@RestInit`, `@RestPostInit`, and
`@RestPostInitChildFirst` hooks run before `addChild` returns.
+When a child is removed, its `@RestDestroy` hooks run and the resource's
`Servlet.destroy()` is invoked (if applicable) — including for any
grandchildren mounted underneath.
+
+Mutations are atomic: route matching either sees the full pre-add state or the
full post-add state, never a partially-mounted child.
+Adding a second child at the same path without `replace=true` throws
`IllegalStateException` to prevent silent route shadowing.
diff --git a/pages/topics/12.15.NextGenRestClient.md
b/pages/topics/12.15.NextGenRestClient.md
new file mode 100644
index 0000000000..0a4ecd2d48
--- /dev/null
+++ b/pages/topics/12.15.NextGenRestClient.md
@@ -0,0 +1,237 @@
+---
+title: "Next-Generation REST Client (Beta)"
+slug: NextGenRestClient
+---
+
+:::warning Beta API
+All public types under `org.apache.juneau.ng.*` are **beta**. Source- and
binary-incompatible changes
+may appear in the next major Juneau release. For production deployments that
require strict
+binary stability, continue using the classic `RestClient` /
`juneau-rest-common` APIs until the
+NG stack is declared stable in a future release.
+:::
+
+Juneau 9.5.0 introduces a next-generation REST client and HTTP type stack under
+`org.apache.juneau.ng.*` that **decouples Juneau from Apache HttpClient 4.5**.
The new stack
+ships alongside the existing `juneau-rest-client` / `juneau-rest-common` APIs
— both continue
+to work unchanged.
+
+The next-generation stack:
+
+- Uses **only JDK types** in its API surface (`org.apache.juneau.ng.http`). No
`org.apache.http.*`
+ on any signature.
+- Introduces an `HttpTransport` abstraction so you can plug in **any HTTP
client**
+ (Apache HC 4.5, Apache HC 5, JDK `HttpClient`, OkHttp, Jetty, or a custom
transport)
+ without changing user code.
+- Replaces 40+ Apache HC–specific builder methods on the classic
`RestClient.Builder` with a
+ small, focused builder. Transport-specific configuration lives on the
**transport builder**,
+ not on `RestClient.Builder`.
+
+---
+
+## Package Layout
+
+| Package | Module | Role |
+|---|---|---|
+| `org.apache.juneau.ng.http` | `juneau-rest-common` | JDK-native HTTP types
(`HttpHeader`, `HttpPart`, `HttpBody`, status types, ~73 RFC-named headers, ~50
named response types, multipart, remote-proxy annotations). No `httpcore`
dependency. |
+| `org.apache.juneau.ng.rest.client` | `juneau-rest-client` | `HttpTransport`
abstraction, `NgRestClient` / `NgRestRequest` / `NgRestResponse`, interceptors,
body converters, `RestLogger`. No `httpcore` dependency. |
+| `org.apache.juneau.ng.rest.mock` | `juneau-rest-mock` | `MockHttpTransport`
and `NgMockRestClient` for in-process testing against a Juneau `RestContext`. |
+
+---
+
+## Transport Modules
+
+Each transport is a separate Maven module pulling in only its native HTTP
client.
+The `HttpTransportProvider` `ServiceLoader` SPI auto-discovers the
highest-priority transport
+on the classpath at `build()` time when no explicit transport is passed.
+
+| Module | Artifact ID | Native client |
+|---|---|---|
+| Apache HttpClient 4.5 | `juneau-ng-rest-client-apache-httpclient-45` |
`org.apache.httpcomponents:httpclient:4.5.x` |
+| Apache HttpClient 5 | `juneau-ng-rest-client-apache-httpclient-50` |
`org.apache.httpcomponents.client5:httpclient5` |
+| JDK `HttpClient` | `juneau-ng-rest-client-java-httpclient` |
`java.net.http.HttpClient` (Java 11+) — zero third-party deps |
+| OkHttp | `juneau-ng-rest-client-okhttp` | `com.squareup.okhttp3:okhttp` |
+| Eclipse Jetty client | `juneau-ng-rest-client-jetty` |
`org.eclipse.jetty:jetty-client` |
+
+Each module ships three classes:
+
+- `*Transport` — the `HttpTransport` implementation.
+- `*TransportBuilder` — extends `HttpTransportBuilder` with
native-client–specific options
+ accessible via cast (e.g.
`ApacheHc45TransportBuilder.httpClientBuilder(HttpClientBuilder)`).
+- `*TransportProvider` — `ServiceLoader` entry for auto-discovery.
+
+---
+
+## Quick Start
+
+```java
+import org.apache.juneau.json5.*;
+import org.apache.juneau.ng.rest.client.*;
+import org.apache.juneau.ng.rest.client.apachehttpclient45.*;
+import java.time.*;
+
+// Pre-build the serializer/parser — configure marshalling externally
+var serializer = Json5Serializer.create().build();
+var parser = Json5Parser.create().build();
+
+// Choose a transport explicitly and configure it
+NgRestClient client = NgRestClient.create()
+ .transportBuilder(ApacheHc45Transport.create()
+ .connectTimeout(Duration.ofSeconds(10))
+ .maxConnections(50))
+ .serializer(serializer)
+ .parser(parser)
+ .rootUrl("https://api.example.com")
+ .build();
+
+// Always use try-with-resources on the request — close releases the connection
+try (var req = client.get("/users/{id}").pathData("id", 42)) {
+ var user = req.run().as(User.class);
+}
+
+// At application shutdown
+client.shutdown();
+```
+
+### Auto-Discovery
+
+If you don't pass an explicit transport, `NgRestClient` uses `ServiceLoader`
to pick the
+highest-priority `HttpTransportProvider` on the classpath:
+
+```java
+NgRestClient zeroConfig = NgRestClient.create()
+ .marshaller(Json5.DEFAULT)
+ .rootUrl("https://api.example.com")
+ .build();
+```
+
+---
+
+## Key Design Differences from the Classic `RestClient`
+
+| Concern | Classic `RestClient` | `NgRestClient` |
+|---|---|---|
+| HTTP integration | Implements `org.apache.http.client.HttpClient`; bound to
Apache HC 4.5 | Composes an `HttpTransport`; transport-agnostic |
+| Serialization model | Multi-language marshallers / format shortcuts
(`json()`, `xml()`, …) | **One pre-built serializer/parser/marshaller per
client**; no multi-language mode |
+| Builder surface | 40+ Apache HC passthrough methods | Small builder with
only Juneau-specific concerns |
+| Lifecycle | `RestClient.close()` | Explicit `shutdown()` (not `Closeable`) —
signalling app-lifecycle vs per-call |
+| Per-request lifecycle | `RestClient` and `RestResponse` were `Closeable` |
**Only `NgRestRequest` is `Closeable`**; `close()` releases the connection |
+| Header/part types | Implement `org.apache.http.Header` / `NameValuePair` |
Implement JDK-only `HttpHeader` / `HttpPart` interfaces |
+| Schema validation on `@Query`/`@Header`/`@FormData`/`@Path` | Supported |
**Not supported** — `toString()` + `CollectionFormat` only |
+
+---
+
+## Resource Lifecycle
+
+`NgRestRequest` is the single `Closeable` in the user-facing API. Close it to
release the
+underlying connection and stream:
+
+```java
+// Correct — try-with-resources releases the connection even if run() or as()
throws
+try (var req = client.get("/users")) {
+ return req.run().as(UserList.class);
+}
+
+// At application shutdown
+client.shutdown();
+```
+
+`NgRestResponse` does **not** implement `Closeable` — its lifecycle is owned
by the request.
+
+---
+
+## Multipart File Uploads
+
+`multipart/form-data` is a first-class body type:
+
+```java
+// Fluent multipart upload
+try (var req = client.multipartPost("/upload")
+ .multipartField("album", "vacation")
+ .multipartFile("photo", Path.of("/tmp/beach.jpg"), "image/jpeg")) {
+ req.run();
+}
+
+// Pre-built body (e.g. shared across requests)
+var mp = MultipartBody.builder()
+ .field("note", "hello")
+ .file("attachment", Path.of("report.pdf"), "application/pdf")
+ .build();
+try (var req = client.post("/cases/123/attachments").body(mp)) {
+ req.run();
+}
+```
+
+`formPost()` and `PartList` continue to produce
`application/x-www-form-urlencoded`
+and should **not** be used for binary uploads.
+
+---
+
+## Logging
+
+Logging is framework-agnostic via `java.lang.System.Logger`:
+
+```java
+NgRestClient client = NgRestClient.create()
+ .transportBuilder(JavaHttpTransport.create())
+ .marshaller(Json5.DEFAULT)
+ .logger(BasicRestLogger.of(System.getLogger("myapp.http")))
+ .build();
+
+// Per-request verbose logging
+try (var req = client.get("/users/123").debug()) {
+ req.run();
+}
+```
+
+Custom log entries can be built using named templates
+(`{method}`, `{uri}`, `{status}`, `{reason}`, `{elapsed}`, `{req.headers}`,
`{req.body}`,
+`{res.headers}`, `{res.body}`, `{error}`).
+
+---
+
+## Mock Transport (Serverless Testing)
+
+`MockHttpTransport` routes `TransportRequest` directly to a Juneau
`RestContext` without
+network I/O. Use `NgMockRestClient` for unit tests that exercise
serialization, remote proxies,
+interceptors, and assertions:
+
+```java
+NgRestClient mock = NgMockRestClient.create(MyRestResource.class)
+ .marshaller(Json5.DEFAULT)
+ .pathData("tenantId", "acme")
+ .build();
+
+try (var req = mock.get("/users/123")) {
+ var user = req.run().as(User.class);
+}
+```
+
+---
+
+## Migration Path
+
+The classic `RestClient` and `juneau-rest-common` APIs are **not deprecated**
in 9.5.
+Migration is opt-in and incremental:
+
+1. Add the NG transport module for your preferred HTTP stack as a Maven
dependency.
+2. Replace `RestClient.create()` with `NgRestClient.create()` and adapt
builder calls —
+ most builder methods map 1:1 (`rootUrl`, default headers/query/form/path,
interceptors,
+ marshaller, `errorCodes`, `executorService`).
+3. Wrap each request in **try-with-resources**. Call `shutdown()` on the
client at
+ application shutdown.
+4. If you customized Apache HttpClient through `httpClientBuilder()` /
`connectionManager()` /
+ `defaultRequestConfig()`, move those calls onto the **transport builder**
+ (e.g. `ApacheHc45TransportBuilder`).
+5. If you relied on multi-language marshallers or per-request language
overrides, create
+ one `NgRestClient` per language.
+
+The NG stack will graduate to stable in a future release, at which point the
classic stack
+may be deprecated and eventually removed.
+
+---
+
+## See Also
+
+- [juneau-rest-client Basics](/docs/topics/JuneauRestClientBasics) — the
classic stack
+- [juneau-rest-mock Basics](/docs/topics/JuneauRestMockBasics) — classic mock
client
+- Release notes: [9.5.0](/docs/release-notes/9.5.0) — Next-Generation REST
Client and HTTP Stack
diff --git a/pages/topics/14.09.InjectAwareMicroservice.md
b/pages/topics/14.09.InjectAwareMicroservice.md
new file mode 100644
index 0000000000..a5eae46dad
--- /dev/null
+++ b/pages/topics/14.09.InjectAwareMicroservice.md
@@ -0,0 +1,184 @@
+---
+title: "Inject-Aware Microservice"
+slug: MicroserviceCoreInject
+---
+
+Starting with **9.5.0**, the <a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.html"
target="_blank">Microservice</a>
+class is **inject-aware**. Every microservice owns an internal
+<a
href="/site/apidocs/org/apache/juneau/commons/inject/WritableBeanStore.html"
target="_blank">WritableBeanStore</a> (accessible via
+<a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.html#getBeanStore()"
target="_blank">getBeanStore()</a>),
+populated from `@Configuration` classes registered on the builder. Beans
contributed this way
+can be consumed by other `@Bean` factory methods (via constructor injection),
by subclasses
+(e.g. <a
href="/site/apidocs/org/apache/juneau/microservice/jetty/JettyMicroservice.html"
target="_blank">JettyMicroservice</a>
+auto-discovers `@Rest` servlets), and by user code via
`getBeanStore().getBean(...)`.
+
+<tree>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.Builder.html"
target="_blank">Microservice.Builder</a></java-class></node-0>
+<node-1><java-method><a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.Builder.html#configurations(java.lang.Class[])"
target="_blank">configurations(Class...)</a></java-method> <java-method><a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.Builder.html#beanStore(org.apache.juneau.commons.inject.WritableBeanStore)"
target="_blank">beanStore(WritableBeanStore)</a></java-method></node-1>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.html"
target="_blank">Microservice</a></java-class></node-0>
+<node-1><java-method><a
href="/site/apidocs/org/apache/juneau/microservice/Microservice.html#getBeanStore()"
target="_blank">getBeanStore()</a></java-method></node-1>
+</tree>
+
+## Quick example
+
+```java
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
+
+@Configuration
+public class AppConfig {
+
+ @Bean
+ public MyService myService() {
+ return new MyService("hello");
+ }
+}
+
+public class App {
+ public static void main(String[] args) throws Exception {
+ Microservice.create()
+ .args(args)
+ .configurations(AppConfig.class)
+ .build()
+ .start()
+ .join();
+ }
+}
+```
+
+Inside the running microservice, the bean is available via:
+
+```java
+MyService svc =
Microservice.getInstance().getBeanStore().getBean(MyService.class).orElseThrow();
+```
+
+## What lands in the bean store
+
+After bootstrap completes, the store contains every bean registered by your
`@Configuration` classes
+**plus** the microservice's own resolved values:
+
+| Type | Notes |
+|---|---|
+| <a href="/site/apidocs/org/apache/juneau/microservice/Microservice.html"
target="_blank">Microservice</a> | The microservice instance itself. |
+| <a href="/site/apidocs/org/apache/juneau/commons/runtime/Args.html"
target="_blank">Args</a> | Resolved CLI args. |
+| <a href="/site/apidocs/org/apache/juneau/commons/runtime/ManifestFile.html"
target="_blank">ManifestFile</a> | Resolved manifest. |
+| <a href="/site/apidocs/org/apache/juneau/config/Config.html"
target="_blank">Config</a> | Resolved configuration. |
+| <a href="/site/apidocs/org/apache/juneau/commons/svl/VarResolver.html"
target="_blank">VarResolver</a> | Resolved var resolver with `$A`, `$M`, `$C`
and standard vars wired up. |
+| <a
href="/site/apidocs/org/apache/juneau/microservice/MicroserviceListener.html"
target="_blank">MicroserviceListener</a> | Either the builder-supplied
listener, a `@Bean`-supplied one, or the default. |
+
+In `JettyMicroservice` the store additionally contains:
+
+| Type | Notes |
+|---|---|
+| <a
href="/site/apidocs/org/apache/juneau/microservice/jetty/JettyMicroservice.html"
target="_blank">JettyMicroservice</a> | The Jetty subclass instance. |
+| <a
href="/site/apidocs/org/apache/juneau/microservice/jetty/JettyMicroserviceListener.html"
target="_blank">JettyMicroserviceListener</a> | Jetty-specific listener. |
+| <a
href="/site/apidocs/org/apache/juneau/microservice/jetty/JettyServerFactory.html"
target="_blank">JettyServerFactory</a> | Factory used to build the Jetty
`Server` from `jetty.xml`. |
+| `org.eclipse.jetty.server.Server` | The created Jetty server (after
`createServer()`). |
+
+## Resolution priority
+
+Beans for these core types resolve in this order:
+
+1. **Explicit builder call** — e.g. `.config(myConfig)`, `.args(myArgs)`.
Always wins.
+2. **`@Bean`-supplied value** — picked up if no builder call was made.
+3. **Built-in default** — typical fallbacks: empty `Args`, classpath
`META-INF/MANIFEST.MF`, etc.
+
+Once resolved, the final value is registered into the bean store,
**overwriting** any `@Bean` contribution
+of the same `(type, name)`. This means downstream consumers (other `@Bean`
factory methods, subclasses)
+always see the same `Args` / `Config` / etc. that `Microservice.getArgs()`
returns.
+
+## Discovering `@Rest` servlets in Jetty
+
+`JettyMicroservice.createServer()` walks the bean store for entries registered
under `Servlet.class`,
+filters them to those whose runtime class carries `@Rest`, and mounts each one
at the path declared in
+`@Rest(path = "...")` (falling back to `/` when no path is set).
+
+```java
+@Rest(path = "/api")
+public class ApiServlet extends RestServlet { /* ... */ }
+
+@Configuration
+public class JettyConfig {
+ @Bean
+ Servlet apiServlet() { return new ApiServlet(); } // mounted
automatically at /api/*
+}
+```
+
+> **Note on declared return types** — Juneau's
`BeanStore.getBeansOfType(Class)` is exact-type
+> (not assignable-to). Declare your `@Bean` method return type as `Servlet`
(not the concrete
+> subclass) so the bean is registered under `Servlet.class` and visible to the
auto-mount logic.
+
+### Path collisions are a hard startup failure
+
+Mounting two servlets at the same pathspec — whether from `Jetty/servlets`,
`Jetty/servletMap`,
+`.servlet(...)` on the builder, or `@Bean` discovery — raises a
`RuntimeException` from
+`createServer()` with a message identifying both contributors:
+
+```
+Servlet mount path collision: '/api/*' is already mounted by @Bean ApiServletA;
+refused by @Bean ApiServletB[apiServletDup].
+```
+
+## Bypassing `jetty.xml` entirely
+
+If your `@Configuration` provides a `@Bean Server` whose attribute
`"ServletContextHandler"` is set,
+`createServer()` will skip the `jetty.xml` factory step and use that server
directly:
+
+```java
+@Configuration
+public class JettyConfig {
+
+ @Bean
+ public Server jettyServer() {
+ var server = new Server();
+ var ctx = new ServletContextHandler();
+ ctx.setContextPath("/");
+ server.setAttribute("ServletContextHandler", ctx);
+ server.setHandler(ctx);
+ return server;
+ }
+}
+```
+
+This is useful in tests and in programs that want fully-code-driven Jetty
configuration.
+
+## Lifecycle: `@PostConstruct` and `@PreDestroy`
+
+`@PostConstruct` callbacks fire automatically on any bean instantiated through
the inject framework
+(e.g. when a `@Bean` method constructs a class via constructor injection, or
when `BeanStore.instantiate(X)`
+is used). Note that beans you `return new MyBean()` directly from a `@Bean`
method do **not** pass
+through that path and will not receive a `@PostConstruct` callback for the
returned instance.
+
+`@PreDestroy` callbacks fire on `Microservice.stop()`, which closes the bean
store and walks every
+**resolved** bean (anything that was retrieved via `getBean` /
`getBeansOfType` at least once) in
+LIFO order. Errors thrown from `@PreDestroy` are logged at `WARNING` level
and do not abort the rest
+of the shutdown sequence.
+
+## External bean stores
+
+`Microservice.Builder.beanStore(WritableBeanStore)` lets you supply an
externally-owned store rather
+than letting the microservice construct a fresh one. This is the integration
point for composing a
+microservice into a larger application that already owns a bean store,
including the
+Spring-application-context bridge provided by `juneau-rest-server-springboot`.
+
+```java
+WritableBeanStore parent = new BasicBeanStore();
+parent.addBean(MyService.class, sharedService);
+
+Microservice.create()
+ .beanStore(parent)
+ .configurations(AppConfig.class)
+ .build()
+ .start();
+```
+
+## Back-compat
+
+The inject-aware bootstrap is **fully additive**:
+
+* Every existing builder method continues to work unchanged.
+* Microservices that never call `.configurations(...)` get an empty bean store
and behave identically
+ to pre-9.5.0 behavior.
+* The `Microservice` field-resolution order (builder > bean store >
default) means explicit
+ builder calls always win, so existing apps cannot be silently altered by an
injected `@Bean Config`
+ or `@Bean Args`.
diff --git a/sidebars.ts b/sidebars.ts
index 7aca57ccaf..3411cf7c0d 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1674,6 +1674,11 @@ const sidebars: SidebarsConfig = {
},
],
},
+ {
+ type: 'doc',
+ id:
'topics/12.15.NextGenRestClient',
+ label: '12.15.
Next-Generation REST Client (Beta)',
+ },
],
},
{
@@ -1738,6 +1743,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/14.08.Listeners',
label: '14.8.
Listeners',
},
+ {
+ type: 'doc',
+ id:
'topics/14.09.InjectAwareMicroservice',
+ label: '14.9.
Inject-Aware Microservice',
+ },
],
},
{