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 8b1db0b107 docs: Mustache view module topic page + 9.5.0 release-notes
entry + view-module cross-links (TODO-83)
8b1db0b107 is described below
commit 8b1db0b1072a4a92aeb809287a2d8a86899a47c6
Author: James Bognar <[email protected]>
AuthorDate: Tue May 26 12:50:26 2026 -0400
docs: Mustache view module topic page + 9.5.0 release-notes entry +
view-module cross-links (TODO-83)
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 52 +++++-
pages/topics/10.14d.JspViewSupport.md | 7 +-
pages/topics/10.14e.ThymeleafViewSupport.md | 6 +-
pages/topics/10.14f.MustacheViewSupport.md | 255 ++++++++++++++++++++++++++++
sidebars.ts | 5 +
5 files changed, 320 insertions(+), 5 deletions(-)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index c91c7ad3d3..6944b97074 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -3189,7 +3189,7 @@ See <a
href="/docs/topics/MicroserviceCoreInject">Inject-Aware Microservice</a>
#### `View` interface — engine-agnostic server-side render contract
-- New `org.apache.juneau.rest.view.View` interface. Carries the data a
templating engine needs to render a response: `String getTemplateName()`,
`Map<String, Object> getAttributes()`, and a `default Map<String, String>
getResponseHeaders()` seam. Engine-agnostic — concrete implementations live in
per-engine bridge modules (`juneau-rest-server-view-jsp` ships in 9.5.0;
Thymeleaf / Mustache / FreeMarker bridges are queued behind it).
+- New `org.apache.juneau.rest.view.View` interface. Carries the data a
templating engine needs to render a response: `String getTemplateName()`,
`Map<String, Object> getAttributes()`, and a `default Map<String, String>
getResponseHeaders()` seam. Engine-agnostic — concrete implementations live in
per-engine bridge modules (`juneau-rest-server-view-jsp`,
`juneau-rest-server-view-thymeleaf`, and `juneau-rest-server-view-mustache` all
ship in 9.5.0; a FreeMarker bridge is queued behind them).
- Designed as the stable extension point for the new view-module family. New
methods will be added as `default`-bodied where possible to preserve backward
compatibility with downstream view impls.
```java
@@ -3807,6 +3807,56 @@ public class AppResource extends RestServlet {
See [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) for the full
topic — engine-selection matrix, Spring Boot integration notes, path-traversal
hardening, and known limitations.
+### juneau-rest-server-view-mustache (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-mustache`, adds
[Mustache](https://mustache.github.io/) view-rendering to `juneau-rest-server`
— sibling to `juneau-rest-server-view-jsp` and
`juneau-rest-server-view-thymeleaf`, but for Mustache's intentionally
logic-less template syntax that's portable across JavaScript, Go, Python, and
Ruby front ends. The same `View` interface (see
[juneau-rest-server](#juneau-rest-server)) shipped with 9.5.0 backs all three
bridges. Engine-agnostic [...]
+
+Mustache's core engine has zero servlet-container dependencies — it compiles
to bytecode and renders directly to a `java.io.Writer`. The raw-template mount
under `/mustache/*` works fully under MockRest, Jetty microservices, and Spring
Boot uniformly. The bridge picks up a user-supplied `MustacheFactory`
automatically via `BeanStore.getBean(MustacheFactory.class)`; when no factory
bean is registered the bridge constructs a default `DefaultMustacheFactory`
anchored on the classpath direct [...]
+
+#### New Classes
+
+- **`org.apache.juneau.rest.view.mustache.BasicMustacheResource`** — REST
mixin attachable via `@Rest(mixins=BasicMustacheResource.class)`. Adds a
default `/mustache/*` mount that renders raw `.mustache` templates under the
configured base path, and contributes `MustacheViewRenderer` to the mixin's
response-processor chain. Builder API:
`BasicMustacheResource.create().basePath("/templates/").templateSuffix(".mustache").build()`.
Configurable mount path via SVL `${juneau.mustache.path:mus [...]
+- **`org.apache.juneau.rest.view.mustache.MustacheView`** — `View`
implementation. Immutable value class; fluent builder:
`MustacheView.of("hello").attr("name", name).header("Cache-Control",
"no-store")`. `attr(...)` rejects `null` values to surface caller bugs early.
+- **`org.apache.juneau.rest.view.mustache.MustacheViewRenderer`** —
`ResponseProcessor` that detects `MustacheView`-typed return values and asks
the configured `com.github.mustachejava.MustacheFactory` to compile and execute
them directly onto the response writer. When no Mustache engine is on the
classpath, surfaces `NO_ENGINE_DIAGNOSTIC` naming the missing dependency.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-mustache</artifactId>
+ <version>9.5.0</version>
+</dependency>
+<!-- engine: -->
+<dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>compiler</artifactId>
+ <version>0.9.14</version>
+</dependency>
+```
+
+#### Composition example
+
+```java
+@Rest(path="/app", mixins=BasicMustacheResource.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ BasicMustacheResource mustache() {
+ return BasicMustacheResource.create()
+ .basePath("/templates/")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return MustacheView.of("hello").attr("name", name);
+ }
+}
+```
+
+See [Mustache View Support](/docs/topics/MustacheViewSupport) for the full
topic — engine-selection matrix, Spring Boot integration notes (Spring Boot's
official starter ships `jmustache`, not `mustache.java`), path-traversal
hardening, and known limitations.
+
### juneau-bean-rfc7807 (new module)
A new bean module, `juneau-bean-rfc7807`, provides typed beans for [RFC 7807 —
Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc7807)
(`application/problem+json`). RFC 7807 was obsoleted by [RFC
9457](https://www.rfc-editor.org/rfc/rfc9457) in July 2023, but the data model
and the IANA media-type registration are unchanged.
diff --git a/pages/topics/10.14d.JspViewSupport.md
b/pages/topics/10.14d.JspViewSupport.md
index 44331e313d..110c57b5c0 100644
--- a/pages/topics/10.14d.JspViewSupport.md
+++ b/pages/topics/10.14d.JspViewSupport.md
@@ -11,8 +11,9 @@ The `juneau-rest-server-view-jsp` module adds JSP (JavaServer
Pages) view-render
> This page covers the JSP-specific bridge. For the engine-agnostic
> [`View`](/site/apidocs/org/apache/juneau/rest/view/View.html) interface
> itself, see the
> [9.5.0 release notes](/docs/release-notes/9.5.0) under the
> `juneau-rest-server` section.
-> Sibling view modules (Thymeleaf, Mustache, FreeMarker) are tracked behind
this one and
-> ship the same shape.
+> Sibling view modules [Thymeleaf](/docs/topics/ThymeleafViewSupport) and
+> [Mustache](/docs/topics/MustacheViewSupport) ship the same shape in 9.5.0; a
FreeMarker
+> bridge is queued behind them.
## Why JSP?
@@ -233,5 +234,7 @@ Both subclasses mount independently and each resolves
templates against its own
- [REST Server — Composition (mixins,
paths)](/docs/topics/RestServerComposition)
- [REST Server — Static-Files Mixin](/docs/topics/StaticFilesMixin)
+- [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) — sibling bridge
for Thymeleaf
+- [Mustache View Support](/docs/topics/MustacheViewSupport) — sibling bridge
for Mustache
- [Response Processors](/docs/topics/ResponseProcessors)
- [9.5.0 release notes — `juneau-rest-server-view-jsp` (new
module)](/docs/release-notes/9.5.0)
diff --git a/pages/topics/10.14e.ThymeleafViewSupport.md
b/pages/topics/10.14e.ThymeleafViewSupport.md
index 58a4293f4f..05bfaa48ec 100644
--- a/pages/topics/10.14e.ThymeleafViewSupport.md
+++ b/pages/topics/10.14e.ThymeleafViewSupport.md
@@ -12,8 +12,9 @@ the core.
> This page covers the Thymeleaf-specific bridge. For the engine-agnostic
> [`View`](/site/apidocs/org/apache/juneau/rest/view/View.html) interface
> itself, see the
> [9.5.0 release notes](/docs/release-notes/9.5.0) under the
> `juneau-rest-server` section.
-> The sibling [JSP View Support](/docs/topics/JspViewSupport) page covers the
JSP bridge with
-> the same shape; future Mustache / FreeMarker bridges will ship the same way.
+> The sibling [JSP View Support](/docs/topics/JspViewSupport) and
+> [Mustache View Support](/docs/topics/MustacheViewSupport) pages cover the
JSP and
+> Mustache bridges with the same shape; a future FreeMarker bridge will ship
the same way.
## Why Thymeleaf?
@@ -236,6 +237,7 @@ Both subclasses mount independently and each resolves
templates against its own
- [REST Server — Composition (mixins,
paths)](/docs/topics/RestServerComposition)
- [JSP View Support](/docs/topics/JspViewSupport) — sibling bridge for
JSP-based apps
+- [Mustache View Support](/docs/topics/MustacheViewSupport) — sibling bridge
for Mustache
- [Response Processors](/docs/topics/ResponseProcessors)
- [9.5.0 release notes — `juneau-rest-server-view-thymeleaf` (new
module)](/docs/release-notes/9.5.0)
- [Thymeleaf 3 documentation](https://www.thymeleaf.org/documentation.html)
diff --git a/pages/topics/10.14f.MustacheViewSupport.md
b/pages/topics/10.14f.MustacheViewSupport.md
new file mode 100644
index 0000000000..c240f376d5
--- /dev/null
+++ b/pages/topics/10.14f.MustacheViewSupport.md
@@ -0,0 +1,255 @@
+---
+title: "Mustache View Support"
+slug: MustacheViewSupport
+---
+
+# Mustache View Support
+
+The `juneau-rest-server-view-mustache` module adds
[Mustache](https://mustache.github.io/)
+view-rendering to `juneau-rest-server` without bleeding the Mustache engine
dependency into
+the core.
+
+> This page covers the Mustache-specific bridge. For the engine-agnostic
+> [`View`](/site/apidocs/org/apache/juneau/rest/view/View.html) interface
itself, see the
+> [9.5.0 release notes](/docs/release-notes/9.5.0) under the
`juneau-rest-server` section.
+> The sibling [JSP View Support](/docs/topics/JspViewSupport) and
+> [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) pages cover the
JSP and
+> Thymeleaf bridges with the same shape; a future FreeMarker bridge will ship
the same way.
+
+## Why Mustache?
+
+Mustache is the canonical **logic-less** templating engine — a small,
well-defined spec
+implemented identically across two dozen languages. The Java implementation
+[`com.github.spullara.mustache.java:compiler`](https://github.com/spullara/mustache.java)
is
+fast (compiled to bytecode), has zero servlet-container dependencies, and
renders directly
+to a `java.io.Writer`, so it runs unchanged under MockRest, Jetty
microservices, and Spring
+Boot. It's the recommended choice when you want a template syntax that's
intentionally
+restricted (no embedded scripts, no inline expressions) and identical to what
your
+JavaScript / Go / Python / Ruby front ends already use.
+
+## Module contents
+
+| Class | Role |
+|---|---|
+| [`View`](/site/apidocs/org/apache/juneau/rest/view/View.html) (in
`juneau-rest-server` core) | Engine-agnostic contract: `getTemplateName()`,
`getAttributes()`, `getResponseHeaders()`. |
+|
[`BasicMustacheResource`](/site/apidocs/org/apache/juneau/rest/view/mustache/BasicMustacheResource.html)
| Mixin. Mounts `/mustache/*` for raw `.mustache` template requests; registers
`MustacheViewRenderer` on the response-processor chain. Builder:
`basePath(String)` (default `/`), `templateSuffix(String)` (default
`.mustache`). |
+|
[`MustacheView`](/site/apidocs/org/apache/juneau/rest/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/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. |
+
+## Engine-agnostic packaging
+
+`juneau-rest-server-view-mustache` ships **only
`com.github.spullara.mustache.java:compiler`
+in `provided` scope**. No engine is bundled with the bridge module. Consumers
add the engine
+matching their deployment.
+
+### Choosing a `MustacheFactory`
+
+| Deployment | Recommended setup | Maven coordinates |
+|---|---|---|
+| **Juneau microservice / Jetty / MockRest** | Bridge-default
`DefaultMustacheFactory` (built on first use, anchored on the importer's
classloader and the configured `basePath`) |
`com.github.spullara.mustache.java:compiler` |
+| **Spring Boot** | User-supplied `@Bean MustacheFactory` (Spring Boot's
official starter ships [`jmustache`](https://github.com/samskivert/jmustache),
not `mustache.java` — see *Spring Boot integration* below) |
`com.github.spullara.mustache.java:compiler` |
+| **Custom resolvers / caching / preprocessors** | User-supplied `@Bean
MustacheFactory` (subclass `DefaultMustacheFactory` or implement your own) |
Whatever you build on top of `compiler` |
+
+The bridge picks up a `MustacheFactory` bean from the request's `BeanStore` via
+`BeanStore.getBean(MustacheFactory.class)` first; if no factory bean is
registered, it
+constructs a default `DefaultMustacheFactory` rooted at the classpath
directory derived from
+`basePath` (e.g. `basePath("/templates/")` → resource root `templates`).
+
+When no Mustache engine is on the classpath, the renderer surfaces a clear
diagnostic naming
+the missing dependency:
+
+```text
+No Mustache engine is available on the classpath. Add:
+ - com.github.spullara.mustache.java:compiler
+Or register a custom @Bean MustacheFactory that supplies your preferred
resolvers / caching.
+See https://juneau.apache.org/docs/topics/MustacheViewSupport for the full
matrix.
+```
+
+## Hello-world
+
+### Maven
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-mustache</artifactId>
+ <version>9.5.0</version>
+</dependency>
+<!-- engine: -->
+<dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>compiler</artifactId>
+ <version>0.9.14</version>
+</dependency>
+```
+
+### Resource layout
+
+```text
+src/main/resources/
+ templates/
+ hello.mustache
+```
+
+### Mustache template (`hello.mustache`)
+
+```html
+<!DOCTYPE html>
+<html>
+<head><title>Hello</title></head>
+<body>
+<p>Hello, {{name}}!</p>
+</body>
+</html>
+```
+
+### REST resource — `View`-return dispatch
+
+```java
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+import org.apache.juneau.rest.view.*;
+import org.apache.juneau.rest.view.mustache.*;
+
+@Rest(path="/app", mixins=BasicMustacheResource.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ BasicMustacheResource mustache() {
+ return BasicMustacheResource.create()
+ .basePath("/templates/")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return MustacheView.of("hello").attr("name", name);
+ }
+}
+```
+
+`GET /app/hello/world` returns `Hello, world!` — `MustacheViewRenderer`
intercepts the
+`MustacheView` return, hands the view's attributes to the active
`MustacheFactory`, and asks
+the compiled `Mustache` to render `/templates/hello.mustache` directly onto
the response.
+
+### REST resource — raw-template mount
+
+The mixin also installs a default `/mustache/*` mount that renders raw
templates under the
+configured base path. With `basePath("/templates/")`, a request for
+`GET /app/mustache/about` renders `/templates/about.mustache` directly — no
Java handler
+required. The handler appends the configured `templateSuffix` if the requested
path doesn't
+already end with it, so `/app/mustache/about.mustache` works too.
+
+The greedy `/*` handler is excluded from the generated Swagger / OpenAPI spec
via
+`@OpSwagger(ignore=true)` since the path isn't API-meaningful.
+
+### Configurable mount path (SVL)
+
+The default mount `/mustache/*` can be overridden via the SVL variable
+`${juneau.mustache.path:mustache}` — set via system property
+(`-Djuneau.mustache.path=views`), environment variable
(`JUNEAU_MUSTACHE_PATH=views`), or
+`Config` key (`juneau.mustache.path = views`) to change the runtime mount
without
+subclassing.
+
+## Path-traversal protection
+
+The raw-template handler funnels every user-supplied `@Path("/*") String path`
through
+[`FileUtils.resolveVirtualPathSafely(String,
String)`](/site/apidocs/org/apache/juneau/commons/utils/FileUtils.html#resolveVirtualPathSafely-java.lang.String-java.lang.String-)
+and rejects any `..` traversal that escapes the configured `basePath` with
HTTP 403. Both
+direct (`/mustache/../secret`) and nested (`/mustache/a/b/../../../secret`)
traversal
+attempts are blocked at the handler boundary before reaching the engine
resolver. This is
+the same hardening the JSP and Thymeleaf bridges apply; see
+[`BasicMustacheResource_PathTraversal_Test`](https://github.com/apache/juneau/blob/main/juneau-utest/src/test/java/org/apache/juneau/rest/view/mustache/BasicMustacheResource_PathTraversal_Test.java)
+for the canonical coverage.
+
+## Spring Boot integration
+
+Spring Boot's official server-side templating starter for Mustache
+(`spring-boot-starter-mustache`) ships
[`jmustache`](https://github.com/samskivert/jmustache),
+**not** `com.github.spullara.mustache.java:compiler`. The bridge module
deliberately targets
+`mustache.java` (the implementation `spring-boot-starter-mustache` does *not*
autowire), so
+Spring Boot applications wiring `BasicMustacheResource` need to register their
own
+`MustacheFactory` `@Bean` explicitly:
+
+```java
+@Configuration
+public class AppConfig {
+
+ @Bean
+ public MustacheFactory mustacheFactory() {
+ return new DefaultMustacheFactory("templates"); //
classpath:/templates/*.mustache
+ }
+
+ @Bean
+ public BasicMustacheResource mustache() {
+ return BasicMustacheResource.create()
+ .basePath("/templates/")
+ .build();
+ }
+}
+```
+
+The Spring `BeanStore` adapter resolves both beans through
`ApplicationContext.getBean(...)`;
+no additional plumbing is required.
+
+### Known constraint — response-processor ordering
+
+Juneau's default response-processor chain runs `SerializedPojoProcessor` ahead
of
+mixin-registered processors. When the host class has a method returning
`MustacheView`, the
+host class needs to add `MustacheViewRenderer` to its *own*
`responseProcessors` list to
+take precedence over the generic POJO serializer. Tracked as a framework
enhancement to add
+a `prepend` mechanism for mixin processors (TODO-96); the real-container
integration tests
+that exercise the `View`-return path under embedded Tomcat / Jetty are
deferred until that
+lands (the Mustache analog of TODO-97 for JSP and TODO-107 for Thymeleaf).
+
+## Multiple base paths
+
+Some apps want `/views/` for the public site and `/admin/views/` for the admin
console.
+Register two `BasicMustacheResource` beans, each in its own subclass with its
own `paths`
+override:
+
+```java
+@Rest(paths={"/views/*"})
+public class PublicViewsResource extends BasicMustacheResource {
+ public PublicViewsResource() {
+ super(BasicMustacheResource.create().basePath("/templates/public/"));
+ }
+}
+
+@Rest(paths={"/admin/views/*"})
+public class AdminViewsResource extends BasicMustacheResource {
+ public AdminViewsResource() {
+ super(BasicMustacheResource.create().basePath("/templates/admin/"));
+ }
+}
+```
+
+Both subclasses mount independently and each resolves templates against its
own `basePath`.
+
+## Limitations and out-of-scope
+
+- **`jmustache` is not supported by the default factory.** Spring Boot's
+ `spring-boot-starter-mustache` autoconfigures `jmustache`'s
`Mustache.Compiler`, which is
+ 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
+ with `mustache.java`. The bridge passes attributes through unchanged.
+
+## See also
+
+- [REST Server — Composition (mixins,
paths)](/docs/topics/RestServerComposition)
+- [JSP View Support](/docs/topics/JspViewSupport) — sibling bridge for
JSP-based apps
+- [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) — sibling bridge
for Thymeleaf
+- [Response Processors](/docs/topics/ResponseProcessors)
+- [9.5.0 release notes — `juneau-rest-server-view-mustache` (new
module)](/docs/release-notes/9.5.0)
+- [mustache.java on GitHub](https://github.com/spullara/mustache.java)
+- [Mustache spec](https://mustache.github.io/mustache.5.html)
diff --git a/sidebars.ts b/sidebars.ts
index c1e8351368..b6e570a239 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1402,6 +1402,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.14e.ThymeleafViewSupport',
label: '10.14e.
Thymeleaf View Support',
},
+ {
+ type: 'doc',
+ id:
'topics/10.14f.MustacheViewSupport',
+ label: '10.14f.
Mustache View Support',
+ },
{
type: 'doc',
id:
'topics/10.15.ClientVersioning',