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 1b85a9530b TODO-351: add 9 additive APIs + full @Remote/@RemoteOp
classic↔NG parity (Section B of the docs-sweep); fix @StringCtor null-context
NPE; JDT cleanup; doc updates
1b85a9530b is described below
commit 1b85a9530bfb99520b2c18478518362a2114c39b
Author: James Bognar <[email protected]>
AuthorDate: Wed Aug 12 14:05:40 2026 -0400
TODO-351: add 9 additive APIs + full @Remote/@RemoteOp classic↔NG parity
(Section B of the docs-sweep); fix @StringCtor null-context NPE; JDT cleanup;
doc updates
---
pages/topics/02.03.JuneauCommonsLang.md | 4 +-
pages/topics/02.09.JuneauCommonsIO.md | 43 +++++++++--------
pages/topics/03.13.02.AutoSwaps.md | 16 +++++++
pages/topics/05.00.JuneauBean.md | 2 +-
pages/topics/05.04.JuneauBeanOpenApi3.md | 5 +-
pages/topics/05.06.JuneauBeanSwagger2.md | 2 +-
pages/topics/05.11.JuneauBeanJsonPatch.md | 19 ++++++--
pages/topics/07.02.00.BeanCentricTesting.md | 10 ++--
pages/topics/07.02.01.CustomErrorMessages.md | 6 +--
pages/topics/07.02.02.00.Customization.md | 10 ++--
pages/topics/07.02.02.01.Stringifiers.md | 12 ++---
pages/topics/07.02.02.02.Listifiers.md | 12 ++---
pages/topics/07.02.02.03.Swappers.md | 8 ++--
pages/topics/07.02.03.PropertyExtractors.md | 24 +++++-----
pages/topics/10.20.StaticFiles.md | 10 +++-
pages/topics/10.21.StaticFilesMixin.md | 12 +++--
pages/topics/10.24.JspViewSupport.md | 22 ++++++++-
pages/topics/10.25.ThymeleafViewSupport.md | 17 ++++++-
pages/topics/10.26.MustacheViewSupport.md | 50 ++++++++++++++++---
pages/topics/10.27.FreemarkerViewSupport.md | 17 ++++++-
pages/topics/13.09.01.Remote.md | 72 +++++++++++++++++++++++++++-
pages/topics/13.09.02.RemoteMethod.md | 37 +++++++++++++-
pages/topics/13.14.NextGenRestClient.md | 29 +++++++++++
23 files changed, 351 insertions(+), 88 deletions(-)
diff --git a/pages/topics/02.03.JuneauCommonsLang.md
b/pages/topics/02.03.JuneauCommonsLang.md
index 4108545fd3..03bfcb05f7 100644
--- a/pages/topics/02.03.JuneauCommonsLang.md
+++ b/pages/topics/02.03.JuneauCommonsLang.md
@@ -160,7 +160,7 @@ int comparison = v1.compareTo(v2); // -1 (v1 < v2)
Version range utility for checking if a version string falls within an
OSGi-style range.
```java
-VersionRange range = new VersionRange("[1.0.0,2.0.0)");
+VersionRange range = VersionRange.of("[1.0.0,2.0.0)");
boolean inRange = range.matches("1.5.0"); // true
```
@@ -224,7 +224,7 @@ String found = result.orElse("not found");
```java
String current = "1.2.3";
-VersionRange supported = new VersionRange("[1.0.0,2.0.0)");
+VersionRange supported = VersionRange.of("[1.0.0,2.0.0)");
if (supported.matches(current)) {
// Version is supported
diff --git a/pages/topics/02.09.JuneauCommonsIO.md
b/pages/topics/02.09.JuneauCommonsIO.md
index 4ef48e519f..a423d6e072 100644
--- a/pages/topics/02.09.JuneauCommonsIO.md
+++ b/pages/topics/02.09.JuneauCommonsIO.md
@@ -23,6 +23,11 @@ InputStream is2 = fsFile.read();
LocalFile cachedFile = new LocalFile(MyClass.class, "template.html");
cachedFile.cache(); // Cache contents in memory
InputStream is3 = cachedFile.read(); // Fast - uses cache
+
+// Check existence without triggering an exception
+if (fsFile.exists()) {
+ InputStream is4 = fsFile.read();
+}
```
### <java-class><a
href="/site/apidocs/org/apache/juneau/commons/io/LocalDir.html"
target="_blank">LocalDir</a></java-class>
@@ -47,8 +52,9 @@ Fluent builder for creating `Reader` instances from files.
```java
FileReaderBuilder builder = FileReaderBuilder.create()
- .file("file.txt")
- .charset(StandardCharsets.UTF_8);
+ .file(Paths.get("file.txt"))
+ .charset(StandardCharsets.UTF_8)
+ .allowNoFile(true); // Don't throw if the file doesn't exist
Reader reader = builder.build();
```
@@ -58,10 +64,10 @@ Fluent builder for creating `Writer` instances for files.
```java
FileWriterBuilder builder = FileWriterBuilder.create()
- .file("output.txt")
+ .file(Paths.get("output.txt"))
.charset(StandardCharsets.UTF_8)
- .append()
- .buffered();
+ .append(true)
+ .buffered(true);
Writer writer = builder.build();
```
@@ -72,7 +78,8 @@ Fluent builder for creating `Reader` instances from `Path`
objects.
```java
PathReaderBuilder builder = PathReaderBuilder.create()
.path(Paths.get("file.txt"))
- .charset(StandardCharsets.UTF_8);
+ .charset(StandardCharsets.UTF_8)
+ .allowNoFile(true); // Don't throw if the file doesn't exist
Reader reader = builder.build();
```
@@ -149,16 +156,14 @@ wrapped.close(); // Does not close the original writer
### Reading Files from Classpath or File System
```java
-// Try classpath first, then file system
+// Try classpath first, then file system - use exists() to check without
+// relying on exception handling for control flow.
LocalFile file = new LocalFile(MyClass.class, "config.properties");
-try (InputStream is = file.read()) {
+LocalFile fsFile = new LocalFile(Paths.get("/etc/myapp/config.properties"));
+LocalFile resolved = file.exists() ? file : fsFile;
+
+try (InputStream is = resolved.read()) {
// Read file contents
-} catch (IOException e) {
- // Not found on the classpath - fall back to the file system
- LocalFile fsFile = new
LocalFile(Paths.get("/etc/myapp/config.properties"));
- try (InputStream is = fsFile.read()) {
- // Read file contents
- }
}
```
@@ -189,9 +194,9 @@ LocalFile about = templates.resolve("about.html");
### File I/O with Builders
```java
-// Read file with specific charset
+// Read file with specific charset, using a Path
try (Reader reader = FileReaderBuilder.create()
- .file("file.txt")
+ .file(Paths.get("file.txt"))
.charset(StandardCharsets.UTF_8)
.build()) {
// Read from file
@@ -199,10 +204,10 @@ try (Reader reader = FileReaderBuilder.create()
// Write file with append mode
try (Writer writer = FileWriterBuilder.create()
- .file("output.txt")
+ .file(Paths.get("output.txt"))
.charset(StandardCharsets.UTF_8)
- .append()
- .buffered()
+ .append(true)
+ .buffered(true)
.build()) {
writer.write("New content");
}
diff --git a/pages/topics/03.13.02.AutoSwaps.md
b/pages/topics/03.13.02.AutoSwaps.md
index 1b5172f6c4..28aa319c62 100644
--- a/pages/topics/03.13.02.AutoSwaps.md
+++ b/pages/topics/03.13.02.AutoSwaps.md
@@ -27,6 +27,22 @@ Note that these methods cover conversion from several
built-in Java types, meani
- `parseString(String)` - <a
href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/DatatypeConverter.html"
target="_blank">DatatypeConverter</a>
- `forName(String)` - <a
href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html"
target="_blank">Class</a>
+If your static factory method doesn't match any of those conventional names
(e.g. `of(String)` or `create(String)`), you can use the <a
href="/site/apidocs/org/apache/juneau/marshall/StringCtor.html"
target="_blank">@StringCtor</a> annotation on the class to name the exact
method to use, short-circuiting the name-based lookup above:
+
+```java
+@StringCtor("of")
+public class Money {
+
+ public static Money of(String value) {
+ return new Money(value);
+ }
+
+ ...
+}
+```
+
+Classes without `@StringCtor` continue to fall back to the conventional-name
lookup (and then the single-`String`-arg constructor) described above.
+
If you want to force a bean-like class to be serialized as a string, you can
use the <a href="/site/apidocs/org/apache/juneau/marshall/Marshalled.html#as()"
target="_blank">@Marshalled(as=STRING)</a> annotation on the class to force it
to be serialized to a string using the `toString()` method.
Serializing to other intermediate objects can be accomplished by defining a
swap method directly on the class:
diff --git a/pages/topics/05.00.JuneauBean.md b/pages/topics/05.00.JuneauBean.md
index 641b6086bc..75ab98cf47 100644
--- a/pages/topics/05.00.JuneauBean.md
+++ b/pages/topics/05.00.JuneauBean.md
@@ -171,7 +171,7 @@ Swagger swagger = swagger()
)
)
.addSchemes("http")
- .addPath("/pet", "post",
+ .addOperation("/pet", "post",
operation()
.setTags("pet")
.setSummary("Add a new pet to the store")
diff --git a/pages/topics/05.04.JuneauBeanOpenApi3.md
b/pages/topics/05.04.JuneauBeanOpenApi3.md
index 53e0221edd..325f708c29 100644
--- a/pages/topics/05.04.JuneauBeanOpenApi3.md
+++ b/pages/topics/05.04.JuneauBeanOpenApi3.md
@@ -134,8 +134,9 @@ A few things to note when comparing this to the
(superficially similar) Swagger
[juneau-bean-swagger2](/docs/topics/JuneauBeanSwagger2):
- OpenAPI v3 paths are added with `OpenApi.addPath(String path, PathItem
pathItem)` — a 2-argument method that
- takes a fully-built `PathItem`. This differs from Swagger v2's
`Swagger.addPath(String path, String method,
- Operation operation)`, a 3-argument method that adds a single operation
directly. Individual HTTP methods on an
+ takes a fully-built `PathItem`. This differs from Swagger v2's
`Swagger.addOperation(String path, String method,
+ Operation operation)` (also available under its original name,
`Swagger.addPath(...)`, for backwards
+ compatibility), a 3-argument method that adds a single operation directly.
Individual HTTP methods on an
OpenAPI v3 `PathItem` are set via
`setGet(Operation)`/`setPost(Operation)`/etc.
- Request bodies use `RequestBodyInfo` (via the `requestBodyInfo()` factory),
not `requestBody()`/`schema()`
static factories — those don't exist in this package.
diff --git a/pages/topics/05.06.JuneauBeanSwagger2.md
b/pages/topics/05.06.JuneauBeanSwagger2.md
index 7edf51ccbc..8e52b12272 100644
--- a/pages/topics/05.06.JuneauBeanSwagger2.md
+++ b/pages/topics/05.06.JuneauBeanSwagger2.md
@@ -46,7 +46,7 @@ Swagger swagger = swagger()
)
)
.addSchemes("http")
- .addPath("/pet", "post",
+ .addOperation("/pet", "post",
operation()
.setTags("pet")
.setSummary("Add a new pet to the store")
diff --git a/pages/topics/05.11.JuneauBeanJsonPatch.md
b/pages/topics/05.11.JuneauBeanJsonPatch.md
index aebae8ee39..dee30ebc82 100644
--- a/pages/topics/05.11.JuneauBeanJsonPatch.md
+++ b/pages/topics/05.11.JuneauBeanJsonPatch.md
@@ -33,7 +33,17 @@ This module models the JSON Patch wire format as typed
`@Marshalled` POJOs with
## Serializer / parser configuration
-The dictionary wiring tells the parser how to map `op` ⇒ subclass, but the
serializer still needs to know to emit the `op` discriminator. Configure both
ends like this:
+The dictionary wiring tells the parser how to map `op` ⇒ subclass, but the
serializer still needs to know to emit the `op` discriminator.
`JsonPatchMarshaller` ships a pre-wired `DEFAULT` serializer/parser pair with
that discriminator already configured, so callers don't need to hand-wire
`typePropertyName(JsonPatchOperation.class,"op")` themselves:
+
+```java
+import org.apache.juneau.bean.jsonpatch.*;
+
+// Pre-configured serializer/parser, wired for the "op" discriminator.
+JsonSerializer ser = JsonPatchMarshaller.SERIALIZER;
+JsonParser par = JsonPatchMarshaller.PARSER;
+```
+
+If you need custom settings, build your own serializer/parser and still apply
the same `typePropertyName` wiring:
```java
import org.apache.juneau.marshall.json.*;
@@ -64,8 +74,9 @@ JsonPatch patch = new JsonPatch()
.append(new MoveOp("/a/b/d", "/a/b/c"))
.append(new CopyOp("/a/b/e", "/a/b/d"));
-String wire = ser.write(patch);
-JsonPatch back = par.read(wire, JsonPatch.class);
+// Static shortcuts on JsonPatchMarshaller use the pre-wired DEFAULT
serializer/parser.
+String wire = JsonPatchMarshaller.of(patch);
+JsonPatch back = JsonPatchMarshaller.to(wire, JsonPatch.class);
```
The wire form is a top-level JSON array of operation objects, matching the
example in [RFC 6902
§3](https://datatracker.ietf.org/doc/html/rfc6902#section-3):
@@ -86,7 +97,7 @@ The wire form is a top-level JSON array of operation objects,
matching the examp
The parser materialises the concrete subclass keyed off the wire `op` value.
Each entry in the parsed `JsonPatch` is an instance of the matching subclass —
no manual instanceof / switch needed:
```java
-JsonPatch back = par.read(wire, JsonPatch.class);
+JsonPatch back = JsonPatchMarshaller.to(wire, JsonPatch.class);
for (JsonPatchOperation op : back) {
if (op instanceof AddOp add) { /* ... */ }
else if (op instanceof RemoveOp rem) { /* ... */ }
diff --git a/pages/topics/07.02.00.BeanCentricTesting.md
b/pages/topics/07.02.00.BeanCentricTesting.md
index abd3aac7bf..141e8dfbe7 100644
--- a/pages/topics/07.02.00.BeanCentricTesting.md
+++ b/pages/topics/07.02.00.BeanCentricTesting.md
@@ -559,7 +559,7 @@ For more details, see [Customization](Customization).
### Custom Bean Converters
-The default bean converter can be customized on a per-thread basis using
`BctConfiguration.set(BeanConverter)` or via the `@BctConfig` annotation. This
is particularly useful in test setup methods to configure a custom converter
for all tests in a test class or method.
+The default bean converter can be customized on a per-thread basis using
`BctAssertions.setConverter(BeanConverter)` (a thin passthrough to
`BctConfiguration.set(BeanConverter)`) or via the `@BctConfig` annotation. This
is particularly useful in test setup methods to configure a custom converter
for all tests in a test class or method.
```java
// Set custom converter in @BeforeEach method
@@ -567,12 +567,12 @@ The default bean converter can be customized on a
per-thread basis using `BctCon
void setUp() {
var converter = BasicBeanConverter.builder()
.defaultSettings()
- .addStringifier(LocalDate.class, date ->
+ .addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
- .addStringifier(Money.class, money ->
+ .addStringifier(Money.class, (conv,money) ->
money.getAmount().toPlainString())
.build();
- BctConfiguration.set(converter);
+ setConverter(converter);
}
// All assertions now use the custom converter
@@ -584,7 +584,7 @@ void testWithCustomConverter() {
// Reset in @AfterEach method
@AfterEach
void tearDown() {
- BctConfiguration.clear();
+ resetConverter();
}
```
diff --git a/pages/topics/07.02.01.CustomErrorMessages.md
b/pages/topics/07.02.01.CustomErrorMessages.md
index 10b1b8dc3f..b33adb1b8d 100644
--- a/pages/topics/07.02.01.CustomErrorMessages.md
+++ b/pages/topics/07.02.01.CustomErrorMessages.md
@@ -199,10 +199,10 @@ import static org.apache.juneau.commons.utils.Shorts.fs;
void setUp() {
var converter = BasicBeanConverter.builder()
.defaultSettings()
- .addStringifier(LocalDate.class, date ->
+ .addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build();
- BctConfiguration.set(converter);
+ setConverter(converter);
}
// Use custom message with the converter
@@ -211,7 +211,7 @@ assertBean(fs("Date validation failed for user %s", userId),
@AfterEach
void tearDown() {
- BctConfiguration.clear();
+ resetConverter();
}
```
diff --git a/pages/topics/07.02.02.00.Customization.md
b/pages/topics/07.02.02.00.Customization.md
index 640805d55a..8e5b5c29ad 100644
--- a/pages/topics/07.02.02.00.Customization.md
+++ b/pages/topics/07.02.02.00.Customization.md
@@ -248,10 +248,10 @@ public static class MyCustomConverter extends
BasicBeanConverter {
super(BasicBeanConverter.builder()
.defaultSettings()
// Add custom stringifier for LocalDate
- .addStringifier(LocalDate.class, date ->
+ .addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
// Add custom stringifier for Money
- .addStringifier(Money.class, money ->
+ .addStringifier(Money.class, (conv,money) ->
money.getAmount().toPlainString())
.build());
}
@@ -277,15 +277,15 @@ class MyTest {
void setUp() {
var converter = BasicBeanConverter.builder()
.defaultSettings()
- .addStringifier(LocalDate.class, date ->
+ .addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
.build();
- BctConfiguration.set(converter);
+ setConverter(converter); // BctAssertions.setConverter(...) - a thin
passthrough to BctConfiguration.set(...)
}
@AfterEach
void tearDown() {
- BctConfiguration.clear(); // Also clears converter
+ resetConverter(); // BctAssertions.resetConverter() - a thin passthrough
to BctConfiguration.clear()
}
```
diff --git a/pages/topics/07.02.02.01.Stringifiers.md
b/pages/topics/07.02.02.01.Stringifiers.md
index 87f6973934..56690e2162 100644
--- a/pages/topics/07.02.02.01.Stringifiers.md
+++ b/pages/topics/07.02.02.01.Stringifiers.md
@@ -109,11 +109,11 @@ var converter = BasicBeanConverter.builder()
.build();
// Usage in tests
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(order, "total,customer", "$99.99,{John Smith
<joh***@example.com>}");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -148,18 +148,18 @@ Stringifier<TreeNode> treeStringifier = new
Stringifier<TreeNode>() {
// Create converter with custom formatting
var converter = BasicBeanConverter.builder()
.defaultSettings()
- .addStringifier(LocalDate.class, date ->
+ .addStringifier(LocalDate.class, (conv,date) ->
date.format(DateTimeFormatter.ISO_LOCAL_DATE))
- .addStringifier(Money.class, money ->
+ .addStringifier(Money.class, (conv,money) ->
money.getAmount().toPlainString())
.build();
// Use in assertions
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(order, "date,total", "2023-12-01,99.99");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
diff --git a/pages/topics/07.02.02.02.Listifiers.md
b/pages/topics/07.02.02.02.Listifiers.md
index 81cfea53d9..fd25c7c9e3 100644
--- a/pages/topics/07.02.02.02.Listifiers.md
+++ b/pages/topics/07.02.02.02.Listifiers.md
@@ -146,22 +146,22 @@ private void collectDepthFirst(TreeNode node,
List<Object> result) {
```java
// Test paginated results
PaginatedResult<User> page = userService.getUsers(pageNumber);
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertList(page, "Alice", "Bob", "Charlie");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
// Test database results
ResultSet rs = statement.executeQuery("SELECT name FROM users");
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertList(rs,
predicate(row -> ((Map)row).get("name").equals("Alice")),
predicate(row -> ((Map)row).get("name").equals("Bob")));
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -169,13 +169,13 @@ try {
```java
// Test collection properties
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(paginatedResult,
"items{#{name}},totalCount",
"[{Alice},{Bob},{Charlie}],3");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
diff --git a/pages/topics/07.02.02.03.Swappers.md
b/pages/topics/07.02.02.03.Swappers.md
index 1c9910aed8..d41c105e2e 100644
--- a/pages/topics/07.02.02.03.Swappers.md
+++ b/pages/topics/07.02.02.03.Swappers.md
@@ -178,7 +178,7 @@ assertBean(futureOrder, "id,total", "456,99.99");
```java
// Test Result wrapper with custom swapper
Result<User> result = userService.createUser(userData);
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(result, "name,email", "Alice,[email protected]");
@@ -190,7 +190,7 @@ ValidationResult<Order> validation =
orderValidator.validate(order);
ValidationResult<Order> invalidValidation =
orderValidator.validate(invalidOrder);
assertList(invalidValidation, "Missing required field: customer", "Invalid
total: -10");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -199,11 +199,11 @@ ValidationResult<Order> invalidValidation =
orderValidator.validate(invalidOrder
```java
// Test lazy computation
LazyValue<Report> lazyReport = new LazyValue<>(() -> generateReport());
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(lazyReport, "title,itemCount", "Monthly Report,150");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
// Swapper ensures the lazy value is evaluated before testing
diff --git a/pages/topics/07.02.03.PropertyExtractors.md
b/pages/topics/07.02.03.PropertyExtractors.md
index 30b1d98ca3..754c4b0989 100644
--- a/pages/topics/07.02.03.PropertyExtractors.md
+++ b/pages/topics/07.02.03.PropertyExtractors.md
@@ -116,11 +116,11 @@ PropertyExtractor aliasExtractor = new
PropertyExtractor() {
};
// Usage
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(user, "fname,lname,email", "John,Doe,[email protected]");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -151,12 +151,12 @@ PropertyExtractor computedExtractor = new
PropertyExtractor() {
};
// Usage
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(user, "computed_fullName,computed_age,computed_initials",
"John Doe,30,J.D.");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -210,11 +210,11 @@ PropertyExtractor privateFieldExtractor = new
PropertyExtractor() {
};
// Usage
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(myBean, "_privateField1,_privateField2", "value1,value2");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -276,12 +276,12 @@ PropertyExtractor configExtractor = new
PropertyExtractor() {
};
// Usage
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(config, "database.host,database.port,app.name",
"localhost,5432,MyApp");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -447,7 +447,7 @@ PropertyExtractor fallbackExtractor = new
PropertyExtractor() {
```java
// Test database entity
DatabaseEntity entity = loadEntity(123);
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(entity, "id,displayName,createdDate", "123,John
Doe,2023-01-15");
@@ -460,7 +460,7 @@ Configuration config = loadConfig();
User user = loadUser(456);
assertBean(user, "computed_fullName,computed_age", "Alice Smith,28");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
@@ -468,12 +468,12 @@ User user = loadUser(456);
```java
// Use property extractor with nested access
-BctConfiguration.set(converter);
+setConverter(converter);
try {
assertBean(order, "customer{computed_fullName},items{0{name}}",
"{John Doe},{{Laptop}}");
} finally {
- BctConfiguration.clear();
+ resetConverter();
}
```
diff --git a/pages/topics/10.20.StaticFiles.md
b/pages/topics/10.20.StaticFiles.md
index dee26eb29d..8d945738ba 100644
--- a/pages/topics/10.20.StaticFiles.md
+++ b/pages/topics/10.20.StaticFiles.md
@@ -8,11 +8,17 @@ The <a
href="/site/apidocs/org/apache/juneau/rest/server/servlet/BasicRestServle
```java
@RestGet(path="/htdocs/*")
public HttpResource getHtdoc(RestRequest req, @Path("/*") String path, Locale
locale) throws NotFound {
- return req.getContext().getStaticFiles().resolve(path,
locale).orElseThrow(NotFound::new);
+ return req.getStaticFiles().resolve(path,
locale).orElseThrow(NotFound::new);
}
```
-The static file finder can be accessed through the following method:
+The static file finder can be accessed through either of the following methods
— the
+`RestRequest` shortcut is a convenience delegate for the `RestContext`
accessor:
+
+<tree>
+<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/rest/server/RestRequest.html"
target="_blank">RestRequest</a></java-class></node-0>
+<node-1><java-method><a
href="/site/apidocs/org/apache/juneau/rest/server/RestRequest.html#getStaticFiles()"
target="_blank">getStaticFiles()</a></java-method></node-1>
+</tree>
<tree>
<node-0><java-class><a
href="/site/apidocs/org/apache/juneau/rest/server/RestContext.html"
target="_blank">RestContext</a></java-class></node-0>
diff --git a/pages/topics/10.21.StaticFilesMixin.md
b/pages/topics/10.21.StaticFilesMixin.md
index ffd14603e0..6197ae2dd9 100644
--- a/pages/topics/10.21.StaticFilesMixin.md
+++ b/pages/topics/10.21.StaticFilesMixin.md
@@ -31,7 +31,7 @@ public class StaticFilesMixin {
swagger = @OpSwagger(ignore = true)
)
public HttpResource getStaticFile(RestRequest req, @Path("/*") String
path, Locale locale) {
- return req.getContext().getStaticFiles().resolve(path,
locale).orElseThrow(NotFound::new);
+ return req.getStaticFiles().resolve(path,
locale).orElseThrow(NotFound::new);
}
@RestOp(
@@ -45,8 +45,10 @@ public class StaticFilesMixin {
}
```
-The handler resolves the `StaticFiles` bean via
`req.getContext().getStaticFiles()` at request time,
-which delegates to `BeanStore.getBean(StaticFiles.class)` and falls back to a
default `BasicStaticFiles` instance
+The handler resolves the `StaticFiles` bean via `req.getStaticFiles()` at
request time — a
+[`RestRequest`](/site/apidocs/org/apache/juneau/rest/server/RestRequest.html)
convenience shortcut for
+`req.getContext().getStaticFiles()` — which delegates to
`BeanStore.getBean(StaticFiles.class)` and falls
+back to a default `BasicStaticFiles` instance
that searches the importer's classpath under `static/` and `htdocs/` plus the
working-directory
`static/` and `htdocs/` folders. Missing paths surface as `NotFound` thrown
from the handler,
which flows through the standard exception-rendering chain (RFC 7807
problem-details when
@@ -155,8 +157,8 @@ public class MyResource extends RestServlet {
}
```
-The `StaticFiles` bean is resolved through the request context via
-`RestContext.getStaticFiles()`, which honors the `@Bean`-factory walk
(microservice
+The `StaticFiles` bean is resolved through the request context via
`req.getStaticFiles()`
+(`RestContext.getStaticFiles()` under the hood), which honors the
`@Bean`-factory walk (microservice
path uses `BasicBeanStore`; Spring Boot path uses `SpringBeanStore` →
`ApplicationContext.getBeanProvider(...)`).
If you register multiple `StaticFiles` beans in a Spring `@Configuration`,
mark exactly one
`@Primary` so the bean store lookup is deterministic.
diff --git a/pages/topics/10.24.JspViewSupport.md
b/pages/topics/10.24.JspViewSupport.md
index 6d2dee82be..0d7a731555 100644
--- a/pages/topics/10.24.JspViewSupport.md
+++ b/pages/topics/10.24.JspViewSupport.md
@@ -27,7 +27,7 @@ the mixin packaging makes this clean.
| Class | Role |
|---|---|
| [`View`](/site/apidocs/org/apache/juneau/rest/server/view/View.html) (in
`juneau-rest-server` core) | Engine-agnostic contract: `getTemplateName()`,
`getAttributes()`, `getResponseHeaders()`. |
-|
[`JspMixin`](/site/apidocs/org/apache/juneau/rest/server/view/jsp/JspMixin.html)
| Mixin. Mounts `/jsp/*` for raw `.jsp` requests; registers `JspViewRenderer`
on the response-processor chain. Builder: `basePath(String)` (default `/`). |
+|
[`JspMixin`](/site/apidocs/org/apache/juneau/rest/server/view/jsp/JspMixin.html)
| Mixin. Mounts `/jsp/*` for raw `.jsp` requests; registers `JspViewRenderer`
on the response-processor chain. Builder: `basePath(String)` (default `/`),
`cacheTemplates(boolean)` (accepted for
[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html)
conformance; no-op — see below). |
|
[`JspView`](/site/apidocs/org/apache/juneau/rest/server/view/jsp/JspView.html)
| `View` implementation. Immutable; fluent:
`JspView.of("hello.jsp").attr("name", name).header("Cache-Control",
"no-store")`. |
|
[`JspViewRenderer`](/site/apidocs/org/apache/juneau/rest/server/view/jsp/JspViewRenderer.html)
| `ResponseProcessor` that detects `JspView` returns and dispatches via
`ServletContext.getRequestDispatcher(...).forward(...)`. |
@@ -239,6 +239,26 @@ public class AdminViewsResource extends JspMixin {
Both subclasses mount independently and each resolves templates against its own
`basePath`.
+## View-mixin builder contract
+
+`JspMixin.Builder` implements
+[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html),
the
+minimal contract shared by all four view-engine mixin builders
(`basePath(String)` +
+`cacheTemplates(boolean)`):
+
+```java
+import org.apache.juneau.rest.server.view.*;
+
+ViewMixinBuilder<?> anyEngineBuilder = JspMixin.create();
+anyEngineBuilder.basePath("/WEB-INF/views/").cacheTemplates(false);
+```
+
+`cacheTemplates(boolean)` is accepted and reported back (`isCacheTemplates()`)
purely for
+conformance with the shared contract — it has **no effect** on this bridge.
JSP recompilation is
+delegated entirely to the servlet container via
`RequestDispatcher.forward(...)`; there is no
+bridge-owned template cache to disable. Configure hot-reload through your
container instead (e.g.
+Tomcat/Jasper's `development` / `modificationTestInterval`
servlet-init-params).
+
## Limitations and out-of-scope
- **JSF / Facelets** — not planned. JSF is a separate concern; if ever
requested, ships
diff --git a/pages/topics/10.25.ThymeleafViewSupport.md
b/pages/topics/10.25.ThymeleafViewSupport.md
index 3d13d640e3..8589a89c11 100644
--- a/pages/topics/10.25.ThymeleafViewSupport.md
+++ b/pages/topics/10.25.ThymeleafViewSupport.md
@@ -29,7 +29,7 @@ It also has zero servlet-container dependencies: the core
engine renders directl
| Class | Role |
|---|---|
| [`View`](/site/apidocs/org/apache/juneau/rest/server/view/View.html) (in
`juneau-rest-server` core) | Engine-agnostic contract: `getTemplateName()`,
`getAttributes()`, `getResponseHeaders()`. |
-|
[`ThymeleafMixin`](/site/apidocs/org/apache/juneau/rest/server/view/thymeleaf/ThymeleafMixin.html)
| Mixin. Mounts `/thymeleaf/*` for raw `.html` template requests; registers
`ThymeleafViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `cacheTemplates(boolean)` (default `true`),
`templateMode(TemplateMode)` (default `HTML`). |
+|
[`ThymeleafMixin`](/site/apidocs/org/apache/juneau/rest/server/view/thymeleaf/ThymeleafMixin.html)
| Mixin. Mounts `/thymeleaf/*` for raw `.html` template requests; registers
`ThymeleafViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `cacheTemplates(boolean)` (default `true`),
`templateMode(TemplateMode)` (default `HTML`). Builder implements the shared
[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html)
c [...]
|
[`ThymeleafView`](/site/apidocs/org/apache/juneau/rest/server/view/thymeleaf/ThymeleafView.html)
| `View` implementation. Immutable; fluent:
`ThymeleafView.of("hello").attr("name", name).header("Cache-Control",
"no-store")`. |
|
[`ThymeleafViewRenderer`](/site/apidocs/org/apache/juneau/rest/server/view/thymeleaf/ThymeleafViewRenderer.html)
| `ResponseProcessor` that detects `ThymeleafView` returns and asks the
configured `org.thymeleaf.TemplateEngine` to `process(templateName, context,
writer)` directly onto the response. |
@@ -243,6 +243,21 @@ public class AdminViewsResource extends ThymeleafMixin {
Both subclasses mount independently and each resolves templates against its
own `basePath`.
+## View-mixin builder contract
+
+`ThymeleafMixin.Builder` implements
+[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html),
the
+minimal contract shared by all four view-engine mixin builders
(`basePath(String)` +
+`cacheTemplates(boolean)`). Code written against the interface works unchanged
if you later swap
+engines:
+
+```java
+import org.apache.juneau.rest.server.view.*;
+
+ViewMixinBuilder<?> anyEngineBuilder = ThymeleafMixin.create();
+anyEngineBuilder.basePath("/templates/").cacheTemplates(false);
+```
+
## Limitations and out-of-scope
- **`thymeleaf-spring6` is not a direct dep of the bridge module.** The bridge
depends only
diff --git a/pages/topics/10.26.MustacheViewSupport.md
b/pages/topics/10.26.MustacheViewSupport.md
index 4ad16ef88f..36707bcec4 100644
--- a/pages/topics/10.26.MustacheViewSupport.md
+++ b/pages/topics/10.26.MustacheViewSupport.md
@@ -32,7 +32,7 @@ JavaScript / Go / Python / Ruby front ends already use.
| Class | Role |
|---|---|
| [`View`](/site/apidocs/org/apache/juneau/rest/server/view/View.html) (in
`juneau-rest-server` core) | Engine-agnostic contract: `getTemplateName()`,
`getAttributes()`, `getResponseHeaders()`. |
-|
[`MustacheMixin`](/site/apidocs/org/apache/juneau/rest/server/view/mustache/MustacheMixin.html)
| Mixin. Mounts `/mustache/*` for raw `.mustache` template requests; registers
`MustacheViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `templateSuffix(String)` (default
`.mustache`). |
+|
[`MustacheMixin`](/site/apidocs/org/apache/juneau/rest/server/view/mustache/MustacheMixin.html)
| Mixin. Mounts `/mustache/*` for raw `.mustache` template requests; registers
`MustacheViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `templateSuffix(String)` (default
`.mustache`), `cacheTemplates(boolean)` (default `true`). Builder implements
the shared
[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html)
c [...]
|
[`MustacheView`](/site/apidocs/org/apache/juneau/rest/server/view/mustache/MustacheView.html)
| `View` implementation. Immutable; fluent:
`MustacheView.of("hello").attr("name", name).header("Cache-Control",
"no-store")`. |
|
[`MustacheViewRenderer`](/site/apidocs/org/apache/juneau/rest/server/view/mustache/MustacheViewRenderer.html)
| `ResponseProcessor` that detects `MustacheView` returns and asks the
configured `com.github.mustachejava.MustacheFactory` to compile and execute the
template directly onto the response writer. |
@@ -173,6 +173,49 @@ The default mount `/mustache/*` can be overridden via the
SVL variable
`Config` key (`juneau.mustache.path = views`) to change the runtime mount
without
subclassing.
+## Template caching
+
+`Builder#cacheTemplates(boolean)` (default `true`) controls whether the
bridge-default
+`MustacheFactory` (built lazily when no `@Bean MustacheFactory` is registered)
caches compiled
+templates:
+
+```java
+@Bean
+MustacheMixin mustache() {
+ return MustacheMixin.create()
+ .basePath("/templates/")
+ .cacheTemplates(false) // dev hot-reload: rebuild the factory on
every render
+ .build();
+}
+```
+
+- `true` (default, production-safe) — the bridge builds the default factory
once and reuses it;
+ mustache.java's own `DefaultMustacheFactory` compiles-and-caches templates
internally, so a
+ template is compiled exactly once.
+- `false` — the bridge rebuilds the default factory (with an empty
compile-cache) on every
+ render, so edited `.mustache` files under `basePath` are picked up without a
server restart.
+
+The flag only affects the bridge-default factory; a user-supplied `@Bean
MustacheFactory`
+manages its own caching and is returned as-is regardless of this setting. This
is the same knob
+the sibling JSP / Thymeleaf / FreeMarker bridges expose via the shared
+[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html)
+contract — see [View-mixin builder contract](#view-mixin-builder-contract)
below.
+
+## View-mixin builder contract
+
+`MustacheMixin.Builder` implements
+[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html),
the
+minimal contract shared by all four view-engine mixin builders
(`basePath(String)` +
+`cacheTemplates(boolean)`). Code written against the interface works unchanged
if you later swap
+engines:
+
+```java
+import org.apache.juneau.rest.server.view.*;
+
+ViewMixinBuilder<?> anyEngineBuilder = MustacheMixin.create();
+anyEngineBuilder.basePath("/templates/").cacheTemplates(false);
+```
+
## Path-traversal protection
The raw-template handler funnels every user-supplied `@Path("/*") String path`
through
@@ -255,11 +298,6 @@ Both subclasses mount independently and each resolves
templates against its own
a different API entirely. The bridge module targets `mustache.java`'s
`MustacheFactory`.
Apps preferring `jmustache` can wire a custom `MustacheFactory` `@Bean` that
adapts the
`jmustache` API, but the bridge does not ship that adapter.
-- **No `cacheTemplates(boolean)` knob in 9.5.0.** `DefaultMustacheFactory`
caches compiled
- templates indefinitely and does not expose a runtime disable switch. Apps
that need
- hot-reload during development should register a custom `MustacheFactory`
`@Bean` that
- evicts on its own (e.g. a `DefaultMustacheFactory` rebuilt per request, or
- `InvalidatingMustacheFactory`). Tracked for a follow-on enhancement.
- **Lambda sections are users' responsibility.** Mustache's lambda-section
spec lets a
template attribute resolve to a function; pass the lambda as an attribute via
`MustacheView.attr("greet", new Mustache.Lambda() { ... })` exactly as you
would directly
diff --git a/pages/topics/10.27.FreemarkerViewSupport.md
b/pages/topics/10.27.FreemarkerViewSupport.md
index 1beb8dd7f0..cacdd513f5 100644
--- a/pages/topics/10.27.FreemarkerViewSupport.md
+++ b/pages/topics/10.27.FreemarkerViewSupport.md
@@ -33,7 +33,7 @@ matching Juneau's own license stance.
| Class | Role |
|---|---|
| [`View`](/site/apidocs/org/apache/juneau/rest/server/view/View.html) (in
`juneau-rest-server` core) | Engine-agnostic contract: `getTemplateName()`,
`getAttributes()`, `getResponseHeaders()`. |
-|
[`FreemarkerMixin`](/site/apidocs/org/apache/juneau/rest/server/view/freemarker/FreemarkerMixin.html)
| Mixin. Mounts `/freemarker/*` for raw `.ftl` / `.ftlh` template requests;
registers `FreemarkerViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `templateSuffix(String)` (default `""`),
`cacheTemplates(boolean)` (default `true`). |
+|
[`FreemarkerMixin`](/site/apidocs/org/apache/juneau/rest/server/view/freemarker/FreemarkerMixin.html)
| Mixin. Mounts `/freemarker/*` for raw `.ftl` / `.ftlh` template requests;
registers `FreemarkerViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `templateSuffix(String)` (default `""`),
`cacheTemplates(boolean)` (default `true`). Builder implements the shared
[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder
[...]
|
[`FreemarkerView`](/site/apidocs/org/apache/juneau/rest/server/view/freemarker/FreemarkerView.html)
| `View` implementation. Immutable; fluent:
`FreemarkerView.of("hello.ftlh").attr("name", name).header("Cache-Control",
"no-store")`. |
|
[`FreemarkerViewRenderer`](/site/apidocs/org/apache/juneau/rest/server/view/freemarker/FreemarkerViewRenderer.html)
| `ResponseProcessor` that detects `FreemarkerView` returns and asks the
configured `freemarker.template.Configuration` to
`getTemplate(name).process(dataModel, writer)` directly onto the response
writer. |
@@ -288,6 +288,21 @@ public class AdminViewsResource extends FreemarkerMixin {
Both subclasses mount independently and each resolves templates against its own
`basePath`.
+## View-mixin builder contract
+
+`FreemarkerMixin.Builder` implements
+[`ViewMixinBuilder`](/site/apidocs/org/apache/juneau/rest/server/view/ViewMixinBuilder.html),
the
+minimal contract shared by all four view-engine mixin builders
(`basePath(String)` +
+`cacheTemplates(boolean)`). Code written against the interface works unchanged
if you later swap
+engines:
+
+```java
+import org.apache.juneau.rest.server.view.*;
+
+ViewMixinBuilder<?> anyEngineBuilder = FreemarkerMixin.create();
+anyEngineBuilder.basePath("/templates/").cacheTemplates(false);
+```
+
## Limitations and out-of-scope
- **No bridge-default `Configuration` mutation for user beans.** The
diff --git a/pages/topics/13.09.01.Remote.md b/pages/topics/13.09.01.Remote.md
index 8b062425a8..12b878a160 100644
--- a/pages/topics/13.09.01.Remote.md
+++ b/pages/topics/13.09.01.Remote.md
@@ -105,6 +105,13 @@ public class MyHeaderList extends HeaderList {
```
:::
+:::note Engine-specific
+`headerList` is honored only by the **classic** proxy engine
(`RestClient.getRemote(...)`). Its value type is the
+classic Apache-HttpClient `HeaderList`, which is transport-specific, so the
next-generation engine cannot honor it and
+emits a one-time build-time warning if it is set. Use `headers` for
transport-agnostic constant headers that work on
+both engines.
+:::
+
## @Remote(version/versionHeader)
The <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#version()"
target="_blank">@Remote(version)</a> and <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#versionHeader()"
target="_blank">@Remote(versionHeader)</a> annotations are used to specify the
client-side version of this interface that can be used on the server side to
perform version-specific handling.
@@ -138,4 +145,67 @@ public Object foo() {...}
:::info See Also
[Client Versioning](/docs/topics/ClientVersioning)
-:::
\ No newline at end of file
+:::
+
+## Request-policy members
+
+`@Remote` also supports a set of interface-level request-policy members. As of
10.0.0 these are honored by **both**
+the classic (`RestClient.getRemote(...)`) and next-generation proxy engines —
previously several of them were silently
+ignored by the classic engine.
+
+| Member | Purpose |
+|---|---|
+| <a href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#baseUrl()"
target="_blank">baseUrl</a> | A base/host override applied to every operation
(in place of the client root URL). Must use the `http`/`https` scheme. |
+| <a href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#accept()"
target="_blank">accept</a> | Default `Accept` header (and response-parser
fallback) for every operation. |
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#contentType()"
target="_blank">contentType</a> | Default `Content-Type` header (and
request-serializer selector) for every operation. |
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#queryData()"
target="_blank">queryData</a> | Constant `"name=value"` query parameters added
to every request. |
+| <a href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#formData()"
target="_blank">formData</a> | Constant `"name=value"` form-data parameters
added to every request. |
+| <a href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#timeout()"
target="_blank">timeout</a> | Per-call read/socket timeout as a duration string
(e.g. `"30s"`, `"500ms"`). |
+| <a href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#retries()"
target="_blank">retries</a> | Max automatic retry attempts for safe
(idempotent) operations on transport failures and retryable statuses
(`429`/`5xx`). |
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#retryNonIdempotent()"
target="_blank">retryNonIdempotent</a> | Opt non-idempotent verbs
(`POST`/`PATCH`) into automatic retries. |
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#throwOnError()"
target="_blank">throwOnError</a> | Controls error handling for `>= 400`
responses (see below). |
+
+Every one of these can also be set per-operation on
[@RemoteOp](/docs/topics/RemoteMethod) (and the
+`@RemoteGet`/`@RemotePost`/... variants). The method-level value overrides the
interface-level value, and the constant
+`headers`/`queryData`/`formData` entries are merged (method-level entries win
on a name collision).
+
+:::tip Example
+```java
+@Remote(
+ path="/petstore",
+ accept="application/json",
+ timeout="30s",
+ retries=3,
+ queryData="apiKey=$S{petstore.apiKey}"
+)
+public interface PetStore {...}
+```
+:::
+
+### throwOnError
+
+By default (`throwOnError=false`) a proxy call lets an HTTP error response
(status `>= 400`) flow through:
+
+- If the method declares a matching HTTP-response exception in its `throws`
clause (e.g. `throws NotFound` for a
+ `404`), that typed exception is thrown.
+- Otherwise the error body is returned/parsed as the normal return value.
+
+Setting `throwOnError=true` additionally throws a generic
+<a
href="/site/apidocs/org/apache/juneau/http/response/BasicHttpException.html"
target="_blank">BasicHttpException</a>
+for any unmatched `>= 400` response.
+
+:::note
+Methods returning `void` (or `@RemoteOp(returns=STATUS)`) never throw on an
error status — a `void` method discards
+the response entirely and a `STATUS` method reports the code as its return
value.
+:::
+
+## Engine parity
+
+As of 10.0.0 every `@Remote`/`@RemoteOp` member is honored by both proxy
engines, **except** for two members that are
+genuinely engine-specific. Rather than silently ignore them, the proxy emits a
one-time build-time warning when the
+member is set on the engine that cannot honor it:
+
+| Member | Honored by | On the other engine |
+|---|---|---|
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#interceptors()"
target="_blank">interceptors</a> | next-generation | The **classic** engine
emits a build-time warning — the classic and next-generation
`RestCallInterceptor` SPI types are nominally incompatible (a single class
cannot implement both). |
+| <a
href="/site/apidocs/org/apache/juneau/http/remote/Remote.html#headerList()"
target="_blank">headerList</a> | classic | The **next-generation** engine emits
a build-time warning — its value type is the classic transport-specific
`HeaderList`. Use `headers` for transport-agnostic constant headers. |
\ No newline at end of file
diff --git a/pages/topics/13.09.02.RemoteMethod.md
b/pages/topics/13.09.02.RemoteMethod.md
index c0bc2325ba..29781cc705 100644
--- a/pages/topics/13.09.02.RemoteMethod.md
+++ b/pages/topics/13.09.02.RemoteMethod.md
@@ -87,7 +87,8 @@ The return type of the Java methods can be any of the
following:
- **void/<a
href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Void.html"
target="_blank">Void</a>**
- Don't parse any response.
- - Note that the method will still throw a runtime exception if an error HTTP
status is returned.
+ - The response is discarded entirely, so a `void` method never throws on an
error HTTP status. Use a non-void return
+ type (with a declared HTTP-response exception in the `throws` clause, or
`throwOnError`) if you need error handling.
- **Any parseable POJO**
- The body of the response will be converted to the POJO using the parser
defined on the `RestClient` based on the `Content-Type` of the response.
@@ -124,3 +125,37 @@ public interface PetStore {
If your `RestClient` does not have a parser associated with it, then the value
is converted directly from a String using
the rules defined in [POJO Categories](/docs/topics/PojoCategories).
+
+## Method-level request policy
+
+In addition to `method`/`path`/`returns`, `@RemoteOp` (and the
`@RemoteGet`/`@RemotePost`/`@RemotePut`/`@RemotePatch`/`@RemoteDelete`
+variants) supports the same request-policy members as
[@Remote](/docs/topics/Remote): `headers`, `queryData`, `formData`,
+`accept`, `contentType`, `baseUrl`, `timeout`, `retries`,
`retryNonIdempotent`, and `throwOnError`. A method-level value
+overrides the interface-level value, and constant
`headers`/`queryData`/`formData` entries are merged with the
+interface-level entries (method-level wins on a name collision). As of 10.0.0
these are honored by both the classic and
+next-generation proxy engines.
+
+An additional call-time endpoint override is available via the
+<a href="/site/apidocs/org/apache/juneau/http/Url.html"
target="_blank">@Url</a> parameter annotation, which replaces the
+resolved URL for that single call (it takes precedence over `baseUrl`, and —
like `baseUrl` — must resolve to an
+`http`/`https` URL).
+
+:::tip Example
+```java
+@Remote(path="/petstore")
+public interface PetStore {
+
+ // Per-operation Accept + retry policy, overriding any interface-level
defaults.
+ @RemoteGet(path="/pets", accept="application/json", retries=3)
+ Pet[] getPets();
+
+ // Call-time endpoint override.
+ @RemoteGet
+ Pet[] getPetsFrom(@Url String url);
+}
+```
+:::
+
+:::info See Also
+[@Remote](/docs/topics/Remote) - Interface-level request policy and
cross-engine parity notes.
+:::
diff --git a/pages/topics/13.14.NextGenRestClient.md
b/pages/topics/13.14.NextGenRestClient.md
index c317c76b9b..bb41e6f925 100644
--- a/pages/topics/13.14.NextGenRestClient.md
+++ b/pages/topics/13.14.NextGenRestClient.md
@@ -151,6 +151,35 @@ call, and `run()` transfers ownership of the connection to
the returned `RestRes
---
+## Typed Response Header Accessors
+
+`ResponseHeader` (obtained via `RestResponse.header(String)`) has a small set
of typed-wrapper
+convenience methods, mirroring the classic stack's `asXxxHeader()` family
without pulling in any
+`org.apache.http.classic.*` types. Each is a thin parse over the header's raw
string value using
+JDK types or the transport-neutral types already in `juneau-rest-common`:
+
+| Method | Return type | Absent / unparseable |
+|---|---|---|
+| `asBoolean()` | `Optional<Boolean>` | empty |
+| `asZonedDateTime()` | `Optional<ZonedDateTime>` | empty (RFC-1123 HTTP date)
|
+| `asUri()` | `Optional<URI>` | empty |
+| `asEntityTag()` | `Optional<EntityTag>` | empty (e.g. `ETag`) |
+| `asEntityTags()` | `EntityTags` (never `null`) | `EntityTags.EMPTY` (e.g.
`If-Match`) |
+| `asStringRanges()` | `StringRanges` (never `null`) | empty (e.g.
`Accept-Encoding`) |
+
+```java
+try (var resp = client.get("/users/123").run()) {
+ Optional<ZonedDateTime> lastModified =
resp.header("Last-Modified").asZonedDateTime();
+ Optional<EntityTag> etag = resp.header("ETag").asEntityTag();
+ EntityTags ifMatch = resp.header("If-Match").asEntityTags();
+}
+```
+
+These sit alongside the existing generic accessors (`asInteger()`, `asLong()`,
`asOptional()`,
+`asCsvList()`); for anything not covered here, parse `getValue()` directly.
+
+---
+
## Declarative Remote-Proxy Features (Next-Gen)
The next-generation engine adds a family of declarative capabilities to the