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 d0ac4ac7e6 docs: FreeMarker view module topic page + 9.5.0
release-notes entry + view-module cross-links (TODO-84)
d0ac4ac7e6 is described below
commit d0ac4ac7e6b7ed8d351afdf2768df799e716d0df
Author: James Bognar <[email protected]>
AuthorDate: Tue May 26 13:25:48 2026 -0400
docs: FreeMarker view module topic page + 9.5.0 release-notes entry +
view-module cross-links (TODO-84)
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/9.5.0.md | 53 ++++-
pages/topics/10.14d.JspViewSupport.md | 7 +-
pages/topics/10.14e.ThymeleafViewSupport.md | 8 +-
pages/topics/10.14f.MustacheViewSupport.md | 8 +-
pages/topics/10.14g.FreemarkerViewSupport.md | 298 +++++++++++++++++++++++++++
sidebars.ts | 5 +
6 files changed, 369 insertions(+), 10 deletions(-)
diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 6944b97074..78c624ac21 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`,
`juneau-rest-server-view-thymeleaf`, and `juneau-rest-server-view-mustache` all
ship in 9.5.0; a FreeMarker bridge is queued behind them).
+- 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`, `juneau-rest-server-view-mustache`, and
`juneau-rest-server-view-freemarker` all ship in 9.5.0; Pha [...]
- 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
@@ -3857,6 +3857,57 @@ public class AppResource extends RestServlet {
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-rest-server-view-freemarker (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-freemarker`, adds [Apache
FreeMarker](https://freemarker.apache.org/) view-rendering to
`juneau-rest-server` — sibling to `juneau-rest-server-view-jsp`,
`juneau-rest-server-view-thymeleaf`, and `juneau-rest-server-view-mustache`.
The same `View` interface (see [juneau-rest-server](#juneau-rest-server))
shipped with 9.5.0 backs all four bridges; with this module, the Phase B
sibling lineup is complete. Engine-agnostic POM stance: the brid [...]
+
+FreeMarker is the engine behind countless admin consoles, code generators,
email templates, and reporting tools — Apache-family alignment (ASF-maintained
engine, same license as Juneau itself), richer expression syntax than
logic-less Mustache, and native HTML auto-escaping via the `.ftlh` template
flavor. The core engine has zero servlet-container dependencies; it asks
`freemarker.template.Configuration` for a `Template` and
`template.process(dataModel, writer)` streams the rendered out [...]
+
+#### New Classes
+
+- **`org.apache.juneau.rest.view.freemarker.BasicFreemarkerResource`** — REST
mixin attachable via `@Rest(mixins=BasicFreemarkerResource.class)`. Adds a
default `/freemarker/*` mount that renders raw `.ftl` / `.ftlh` templates under
the configured base path, and contributes `FreemarkerViewRenderer` to the
mixin's response-processor chain. Builder API:
`BasicFreemarkerResource.create().basePath("/templates/").templateSuffix(".ftlh").cacheTemplates(true).build()`.
Configurable mount path v [...]
+- **`org.apache.juneau.rest.view.freemarker.FreemarkerView`** — `View`
implementation. Immutable value class; fluent builder:
`FreemarkerView.of("hello.ftlh").attr("name", name).header("Cache-Control",
"no-store")`. `attr(...)` rejects `null` values to surface caller bugs early.
+- **`org.apache.juneau.rest.view.freemarker.FreemarkerViewRenderer`** —
`ResponseProcessor` that detects `FreemarkerView`-typed return values and asks
the configured `freemarker.template.Configuration` to
`getTemplate(name).process(dataModel, writer)` directly onto the response
writer. When no FreeMarker engine is on the classpath, surfaces
`NO_ENGINE_DIAGNOSTIC` naming the missing dependency (with a pointer to
`spring-boot-starter-freemarker` for Spring Boot apps).
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-freemarker</artifactId>
+ <version>9.5.0</version>
+</dependency>
+<!-- engine: -->
+<dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>2.3.34</version>
+</dependency>
+```
+
+#### Composition example
+
+```java
+@Rest(path="/app", mixins=BasicFreemarkerResource.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ BasicFreemarkerResource freemarker() {
+ return BasicFreemarkerResource.create()
+ .basePath("/templates/")
+ .templateSuffix(".ftlh")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return FreemarkerView.of("hello").attr("name", name);
+ }
+}
+```
+
+See [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) for the full
topic — engine-selection matrix, Spring Boot integration notes
(`spring-boot-starter-freemarker` autoconfigures a `Configuration` bean the
bridge picks up automatically), `.ftl` vs `.ftlh` auto-escape selection,
template caching, 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 110c57b5c0..c48f39c0fb 100644
--- a/pages/topics/10.14d.JspViewSupport.md
+++ b/pages/topics/10.14d.JspViewSupport.md
@@ -11,9 +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](/docs/topics/ThymeleafViewSupport) and
-> [Mustache](/docs/topics/MustacheViewSupport) ship the same shape in 9.5.0; a
FreeMarker
-> bridge is queued behind them.
+> Sibling view modules [Thymeleaf](/docs/topics/ThymeleafViewSupport),
+> [Mustache](/docs/topics/MustacheViewSupport), and
+> [FreeMarker](/docs/topics/FreemarkerViewSupport) ship the same shape in
9.5.0.
## Why JSP?
@@ -236,5 +236,6 @@ Both subclasses mount independently and each resolves
templates against its own
- [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
+- [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) — sibling
bridge for FreeMarker
- [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 05bfaa48ec..d8faaaf3f0 100644
--- a/pages/topics/10.14e.ThymeleafViewSupport.md
+++ b/pages/topics/10.14e.ThymeleafViewSupport.md
@@ -12,9 +12,10 @@ 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) 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.
+> The sibling [JSP View Support](/docs/topics/JspViewSupport),
+> [Mustache View Support](/docs/topics/MustacheViewSupport), and
+> [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) pages cover
the JSP,
+> Mustache, and FreeMarker bridges with the same shape.
## Why Thymeleaf?
@@ -238,6 +239,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
+- [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) — sibling
bridge for FreeMarker
- [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
index c240f376d5..65330eabe5 100644
--- a/pages/topics/10.14f.MustacheViewSupport.md
+++ b/pages/topics/10.14f.MustacheViewSupport.md
@@ -12,9 +12,10 @@ 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.
+> The sibling [JSP View Support](/docs/topics/JspViewSupport),
+> [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport), and
+> [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) pages cover
the JSP,
+> Thymeleaf, and FreeMarker bridges with the same shape.
## Why Mustache?
@@ -249,6 +250,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
- [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) — sibling bridge
for Thymeleaf
+- [FreeMarker View Support](/docs/topics/FreemarkerViewSupport) — sibling
bridge for FreeMarker
- [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)
diff --git a/pages/topics/10.14g.FreemarkerViewSupport.md
b/pages/topics/10.14g.FreemarkerViewSupport.md
new file mode 100644
index 0000000000..bc90eca922
--- /dev/null
+++ b/pages/topics/10.14g.FreemarkerViewSupport.md
@@ -0,0 +1,298 @@
+---
+title: "FreeMarker View Support"
+slug: FreemarkerViewSupport
+---
+
+# FreeMarker View Support
+
+The `juneau-rest-server-view-freemarker` module adds
+[Apache FreeMarker](https://freemarker.apache.org/) view-rendering to
`juneau-rest-server`
+without bleeding the FreeMarker engine dependency into the core.
+
+> This page covers the FreeMarker-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),
+> [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport), and
+> [Mustache View Support](/docs/topics/MustacheViewSupport) pages cover the
JSP, Thymeleaf,
+> and Mustache bridges with the same shape.
+
+## Why FreeMarker?
+
+Apache FreeMarker is a mature, widely deployed Java templating engine — the
engine behind
+admin consoles, code generators, email templates, and reporting tools in
countless
+production codebases. The reference implementation
+[`org.freemarker:freemarker`](https://freemarker.apache.org/) has zero
servlet-container
+dependencies, renders directly to a `java.io.Writer`, and runs unchanged under
MockRest,
+Jetty microservices, and Spring Boot. It's the recommended choice when you
want a richer
+expression syntax than logic-less Mustache, native HTML auto-escaping
+(`.ftlh` templates), and the Apache-family alignment bonus of an
ASF-maintained engine
+matching Juneau's own license stance.
+
+## 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()`. |
+|
[`BasicFreemarkerResource`](/site/apidocs/org/apache/juneau/rest/view/freemarker/BasicFreemarkerResource.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`). |
+|
[`FreemarkerView`](/site/apidocs/org/apache/juneau/rest/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/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. |
+
+## Engine-agnostic packaging
+
+`juneau-rest-server-view-freemarker` ships **only `org.freemarker:freemarker`
in `provided`
+scope**. No engine is bundled with the bridge module. Consumers add the engine
matching
+their deployment.
+
+### Choosing a `Configuration`
+
+| Deployment | Recommended setup | Maven coordinates |
+|---|---|---|
+| **Juneau microservice / Jetty / MockRest** | Bridge-default `Configuration`
(built on first use, anchored on the importer's classloader and the configured
`basePath`; pins `IncompatibleImprovements` to `VERSION_2_3_34`, sets
`DefaultEncoding=UTF-8`, uses `HTMLOutputFormat`) | `org.freemarker:freemarker`
|
+| **Spring Boot** | `spring-boot-starter-freemarker` autoconfigures a
`freemarker.template.Configuration` bean the bridge picks up automatically |
`org.springframework.boot:spring-boot-starter-freemarker` |
+| **Custom loaders / output formats / shared template caches** | User-supplied
`@Bean freemarker.template.Configuration` (configure your own `TemplateLoader`,
`OutputFormat`, etc.) | Whatever you build on top of `freemarker` |
+
+The bridge picks up a `Configuration` bean from the request's `BeanStore` via
+`BeanStore.getBean(Configuration.class)` first; if no configuration bean is
registered, it
+constructs a default `Configuration` rooted at the classpath directory derived
from
+`basePath` (e.g. `basePath("/templates/")` → classloader resource root
`/templates`).
+
+When no FreeMarker engine is on the classpath, the renderer surfaces a clear
diagnostic
+naming the missing dependency:
+
+```text
+No Apache FreeMarker engine is available on the classpath. Add:
+ - org.freemarker:freemarker (FreeMarker engine core)
+Or, for Spring Boot:
+ - org.springframework.boot:spring-boot-starter-freemarker
+ (autoconfigures a freemarker.template.Configuration bean the bridge picks
up).
+Or register a custom @Bean freemarker.template.Configuration with whatever
loaders /
+encodings / output formats you need.
+See https://juneau.apache.org/docs/topics/FreemarkerViewSupport for the full
matrix.
+```
+
+## Hello-world
+
+### Maven
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-freemarker</artifactId>
+ <version>9.5.0</version>
+</dependency>
+<!-- engine: -->
+<dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>2.3.34</version>
+</dependency>
+```
+
+### Resource layout
+
+```text
+src/main/resources/
+ templates/
+ hello.ftlh
+```
+
+### FreeMarker template (`hello.ftlh`)
+
+`.ftlh` is FreeMarker's HTML-auto-escaping template flavor — variable
references emit
+HTML-escaped output by default, defending against XSS regressions:
+
+```html
+<!DOCTYPE html>
+<html>
+<head><title>Hello</title></head>
+<body>
+<p>Hello, ${name}!</p>
+</body>
+</html>
+```
+
+Use the `.ftl` extension instead if you want raw (un-escaped) output — typical
for
+plain-text email bodies or JSON-shaped responses where the caller controls
escaping.
+
+### 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.freemarker.*;
+
+@Rest(path="/app", mixins=BasicFreemarkerResource.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ BasicFreemarkerResource freemarker() {
+ return BasicFreemarkerResource.create()
+ .basePath("/templates/")
+ .templateSuffix(".ftlh")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return FreemarkerView.of("hello").attr("name", name);
+ }
+}
+```
+
+`GET /app/hello/world` returns `Hello, world!` — `FreemarkerViewRenderer`
intercepts the
+`FreemarkerView` return, hands the view's attributes to the active
`Configuration` as the
+data model, and asks the resolved `Template` to render `/templates/hello.ftlh`
directly onto
+the response writer. The `.ftlh` suffix is appended idempotently because the
builder
+declared it.
+
+### REST resource — raw-template mount
+
+The mixin also installs a default `/freemarker/*` mount that renders raw
templates under
+the configured base path. With `basePath("/templates/")` and
`templateSuffix(".ftlh")`, a
+request for `GET /app/freemarker/about` renders `/templates/about.ftlh`
directly — no Java
+handler required. The handler appends the configured `templateSuffix` if the
requested path
+doesn't already end with it, so `/app/freemarker/about.ftlh` 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 `/freemarker/*` can be overridden via the SVL variable
+`${juneau.freemarker.path:freemarker}` — set via system property
+(`-Djuneau.freemarker.path=views`), environment variable
(`JUNEAU_FREEMARKER_PATH=views`),
+or `Config` key (`juneau.freemarker.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 (`/freemarker/../secret`) and nested
+(`/freemarker/a/b/../../../secret`) traversal attempts are blocked at the
handler boundary
+before reaching the engine resolver. This is the same hardening the JSP /
Thymeleaf /
+Mustache bridges apply; see
+[`BasicFreemarkerResource_PathTraversal_Test`](https://github.com/apache/juneau/blob/main/juneau-utest/src/test/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerResource_PathTraversal_Test.java)
+for the canonical coverage.
+
+## Template caching
+
+By default the bridge-built `Configuration` pins
+`TemplateUpdateDelayMilliseconds` to `Long.MAX_VALUE` — FreeMarker caches
compiled templates
+indefinitely, which is the production-safe default. Toggle the builder's
+`cacheTemplates(false)` to set the delay to `0`, causing FreeMarker to
re-check the
+underlying resource on every render (suitable for dev hot-reload):
+
+```java
+@Bean
+BasicFreemarkerResource freemarker() {
+ return BasicFreemarkerResource.create()
+ .basePath("/templates/")
+ .templateSuffix(".ftlh")
+ .cacheTemplates(false) // dev hot-reload
+ .build();
+}
+```
+
+This flag only affects the **bridge-default** `Configuration`. User-supplied
+`@Bean Configuration` instances are not mutated; configure caching directly on
your own
+bean.
+
+## Spring Boot integration
+
+Spring Boot ships `spring-boot-starter-freemarker`, which autoconfigures a
+`freemarker.template.Configuration` bean pre-wired with sane defaults
+(classpath:`/templates/` loader, UTF-8 encoding, view caching toggled on
+`spring.freemarker.cache`). The bridge picks that bean up automatically:
+
+```java
+@Configuration
+public class AppConfig {
+
+ // No @Bean Configuration needed — spring-boot-starter-freemarker provides
one.
+
+ @Bean
+ public BasicFreemarkerResource freemarker() {
+ return BasicFreemarkerResource.create()
+ .basePath("/templates/")
+ .templateSuffix(".ftlh")
+ .build();
+ }
+}
+```
+
+The Spring `BeanStore` adapter resolves the autoconfigured `Configuration`
through
+`ApplicationContext.getBean(...)`; no additional plumbing is required. The
bridge default
+is bypassed entirely when the starter is on the classpath.
+
+### 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
`FreemarkerView`,
+the host class needs to add `FreemarkerViewRenderer` 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 FreeMarker analog of TODO-97 for JSP, TODO-107 for Thymeleaf,
and TODO-108
+for Mustache).
+
+## Multiple base paths
+
+Some apps want `/views/` for the public site and `/admin/views/` for the admin
console.
+Register two `BasicFreemarkerResource` beans, each in its own subclass with
its own
+`paths` override:
+
+```java
+@Rest(paths={"/views/*"})
+public class PublicViewsResource extends BasicFreemarkerResource {
+ public PublicViewsResource() {
+ super(BasicFreemarkerResource.create()
+ .basePath("/templates/public/")
+ .templateSuffix(".ftlh"));
+ }
+}
+
+@Rest(paths={"/admin/views/*"})
+public class AdminViewsResource extends BasicFreemarkerResource {
+ public AdminViewsResource() {
+ super(BasicFreemarkerResource.create()
+ .basePath("/templates/admin/")
+ .templateSuffix(".ftlh"));
+ }
+}
+```
+
+Both subclasses mount independently and each resolves templates against its own
+`basePath`.
+
+## Limitations and out-of-scope
+
+- **No bridge-default `Configuration` mutation for user beans.** The
+ `cacheTemplates(boolean)` builder knob only configures the *bridge-default*
+ `Configuration`. If you provide your own `@Bean Configuration` (Spring Boot
autoconfig
+ counts), configure caching directly on that bean — the bridge does not
mutate it.
+- **`.ftlh` is the recommended HTML default.** FreeMarker's auto-escape
selection is by
+ file extension. For HTML responses prefer `.ftlh`; using `.ftl` for HTML
leaves variable
+ references un-escaped and risks XSS regressions when an attribute binding is
later
+ changed to come from user input. The bridge does not force a suffix — that's
the
+ consumer's choice.
+- **Macros, includes, and FreeMarker imports are users' responsibility.** The
bridge
+ passes template names through unchanged to
`Configuration.getTemplate(name)`; macros,
+ `<#import>`, and `<#include>` resolve through whatever `TemplateLoader` your
active
+ `Configuration` is wired with (the bridge default uses a single
classloader-rooted
+ loader anchored on `basePath`).
+
+## 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
+- [Mustache View Support](/docs/topics/MustacheViewSupport) — sibling bridge
for Mustache
+- [Response Processors](/docs/topics/ResponseProcessors)
+- [9.5.0 release notes — `juneau-rest-server-view-freemarker` (new
module)](/docs/release-notes/9.5.0)
+- [Apache FreeMarker](https://freemarker.apache.org/)
+- [FreeMarker template language
reference](https://freemarker.apache.org/docs/index.html)
diff --git a/sidebars.ts b/sidebars.ts
index b6e570a239..07a29cea72 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -1407,6 +1407,11 @@ const sidebars: SidebarsConfig = {
id:
'topics/10.14f.MustacheViewSupport',
label: '10.14f.
Mustache View Support',
},
+ {
+ type: 'doc',
+ id:
'topics/10.14g.FreemarkerViewSupport',
+ label: '10.14g.
FreeMarker View Support',
+ },
{
type: 'doc',
id:
'topics/10.15.ClientVersioning',