Skip to main content

Mustache View Support

The juneau-rest-server-view-mustache module adds Mustache 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 interface itself, see the 10.0.0 release notes under the juneau-rest-server section. The sibling JSP View Support, Thymeleaf View Support, and FreeMarker View Support pages cover the JSP, Thymeleaf, and FreeMarker bridges with the same shape.

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 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​

ClassRole
View (in juneau-rest-server core)Engine-agnostic contract: getTemplateName(), getAttributes(), getResponseHeaders().
MustacheMixinMixin. 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 contract.
MustacheViewView implementation. Immutable; fluent: MustacheView.of("hello").attr("name", name).header("Cache-Control", "no-store").
MustacheViewRendererResponseProcessor 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​

DeploymentRecommended setupMaven coordinates
Juneau microservice / Jetty / MockRestBridge-default DefaultMustacheFactory (built on first use, anchored on the importer's classloader and the configured basePath)com.github.spullara.mustache.java:compiler
Spring BootUser-supplied @Bean MustacheFactory (Spring Boot's official starter ships jmustache, not mustache.java — see Spring Boot integration below)com.github.spullara.mustache.java:compiler
Custom resolvers / caching / preprocessorsUser-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:

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​

<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-view-mustache</artifactId>
<version>${juneau.version}</version>
</dependency>
<!-- engine: -->
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>0.9.14</version>
</dependency>

Resource layout​

src/main/resources/
templates/
hello.mustache

Mustache template (hello.mustache)​

<!DOCTYPE html>
<html>
<head><title>Hello</title></head>
<body>
<p>Hello, {{name}}!</p>
</body>
</html>

REST resource — View-return dispatch​

import org.apache.juneau.http.*;
import org.apache.juneau.rest.server.*;
import org.apache.juneau.rest.server.servlet.*;
import org.apache.juneau.rest.server.view.*;
import org.apache.juneau.rest.server.view.mustache.*;

@Rest(path="/app", mixins=MustacheMixin.class)
public class AppResource extends RestServlet {

@Bean
MustacheMixin mustache() {
return MustacheMixin.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.

Typed handler integration​

To use MustacheView from a host-class @RestGet without adopting the full mixin, add MustacheViewRenderer.class directly to the host's responseProcessors:

@Rest(responseProcessors={MustacheViewRenderer.class})
public class AppResource extends BasicRestServlet {
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return MustacheView.of("mustache-templates/hello.mustache").attr("name", name);
}
}

The ResponseProcessorList partition pass (added in 10.0.0) automatically repositions MustacheViewRenderer to run before SerializedPojoProcessor. Because mustache.java renders directly to a Writer with zero servlet-container dependencies, attribute interpolation ({{name}} → "Alice") works under MockRest — verified end-to-end by MustacheView_TypedHandler_Test#a04_renderInterpolatesAttributesFromTypedHandler.

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.

Template caching​

Builder#cacheTemplates(boolean) (default true) controls whether the bridge-default MustacheFactory (built lazily when no @Bean MustacheFactory is registered) caches compiled templates:

@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 contract — see View-mixin builder contract below.

View-mixin builder contract​

MustacheMixin.Builder implements ViewMixinBuilder, 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:

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 FileUtils.resolveVirtualPathSafely(String, 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 MustacheMixin_PathTraversal_Test for the canonical coverage.

Spring Boot integration​

Spring Boot's official server-side templating starter for Mustache (spring-boot-starter-mustache) ships 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 MustacheMixin need to register their own MustacheFactory @Bean explicitly:

@Configuration
public class AppConfig {

@Bean
public MustacheFactory mustacheFactory() {
return new DefaultMustacheFactory("templates"); // classpath:/templates/*.mustache
}

@Bean
public MustacheMixin mustache() {
return MustacheMixin.create()
.basePath("/templates/")
.build();
}
}

The Spring BeanStore adapter resolves both beans through ApplicationContext.getBean(...); no additional plumbing is required.

Renderer ordering (resolved in 10.0.0)​

MustacheViewRenderer implements ViewRenderer and is automatically positioned before SerializedPojoProcessor by the ResponseProcessorList partition pass. A @RestOp method returning MustacheView reaches the renderer without any extra configuration — the mixin registers the renderer and the framework ensures ordering. MockRest-level typed-handler integration is verified by MustacheView_TypedHandler_Test (attribute interpolation asserted end-to-end).

Multiple base paths​

Some apps want /views/ for the public site and /admin/views/ for the admin console. Register two MustacheMixin beans, each in its own subclass with its own paths override:

@Rest(paths={"/views/*"})
public class PublicViewsResource extends MustacheMixin {
public PublicViewsResource() {
super(MustacheMixin.create().basePath("/templates/public/"));
}
}

@Rest(paths={"/admin/views/*"})
public class AdminViewsResource extends MustacheMixin {
public AdminViewsResource() {
super(MustacheMixin.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.
  • 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​