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',

Reply via email to