JSP View Support
The juneau-rest-server-view-jsp module adds JSP (JavaServer Pages) view-rendering to
juneau-rest-server without bleeding the JSP-engine dependency (Apache Jasper) into the core.
This page covers the JSP-specific bridge. For the engine-agnostic
Viewinterface itself, see the 10.0.0 release notes under thejuneau-rest-serversection. Sibling view modules Thymeleaf, Mustache, and FreeMarker ship the same shape in 10.0.0.
Why JSP?
JSP is niche but real for legacy migrations into Juneau — particularly internal admin
consoles and reporting tools that already have a tested .jsp template library and don't
want to rewrite to React / Thymeleaf. Before juneau-rest-server-view-jsp, integrating a
JspServlet alongside Juneau's RestServlet meant fighting servlet-mapping precedence;
the mixin packaging makes this clean.
Module contents
| Class | Role |
|---|---|
View (in juneau-rest-server core) | Engine-agnostic contract: getTemplateName(), getAttributes(), getResponseHeaders(). |
JspMixin | Mixin. Mounts /jsp/* for raw .jsp requests; registers JspViewRenderer on the response-processor chain. Builder: basePath(String) (default /), cacheTemplates(boolean) (accepted for ViewMixinBuilder conformance; no-op — see below). |
JspView | View implementation. Immutable; fluent: JspView.of("hello.jsp").attr("name", name).header("Cache-Control", "no-store"). |
JspViewRenderer | ResponseProcessor that detects JspView returns and dispatches via ServletContext.getRequestDispatcher(...).forward(...). |
Engine-agnostic packaging
juneau-rest-server-view-jsp ships only the JSP API + JSTL impl in provided scope.
No JSP engine is bundled with the bridge module. Consumers add the engine matching their
deployment container.
Choosing a JSP engine
| Container | Recommended engine | Maven coordinates |
|---|---|---|
Jetty 12 EE11 — juneau-microservice-jetty, Spring Boot embedded Jetty | Eclipse Jetty's repackaged Jasper bundle | org.eclipse.jetty.ee11:jetty-ee11-apache-jsp |
| Embedded Tomcat — Spring Boot default | Apache Tomcat's embedded Jasper | org.apache.tomcat.embed:tomcat-embed-jasper |
| External WAR deploy — Tomcat / JBoss / WildFly / etc. | Container-supplied | no additional dependency |
When no JSP engine is on the classpath, the renderer surfaces a clear diagnostic naming the missing dependency:
No JSP engine is available on the classpath. Add one of:
- org.eclipse.jetty.ee11:jetty-ee11-apache-jsp (Jetty 12 EE11)
- org.apache.tomcat.embed:tomcat-embed-jasper (embedded Tomcat / Spring Boot default)
- Or rely on the deployment container's bundled engine (Tomcat / JBoss / WildFly / ...)
See https://juneau.apache.org/docs/topics/JspViewSupport for the full matrix.
The underlying JSP engine in all cases is Apache Jasper (the Tomcat project's); Jetty's artifact is just a repackaged Jasper bundle aligned with a specific Jakarta EE generation.
Hello-world
Maven
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-rest-server-view-jsp</artifactId>
<version>${juneau.version}</version>
</dependency>
<!-- pick ONE engine -->
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-apache-jsp</artifactId>
<version>12.1.8</version>
</dependency>
Resource layout
src/main/resources/
META-INF/resources/WEB-INF/views/
hello.jsp
The META-INF/resources/ prefix is the Servlet 3.0 convention that lets embedded
containers (Jetty, Spring-Boot-embedded-Tomcat) discover servlet resources from a JAR.
The same layout works under microservice deployments and Spring Boot fat-jar deployments.
JSP template (hello.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
Hello, ${name}!
REST resource — View-return dispatch
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.jsp.*;
import org.apache.juneau.http.*;
@Rest(path="/app", mixins=JspMixin.class)
public class AppResource extends RestServlet {
@Bean
JspMixin jsp() {
return JspMixin.create()
.basePath("/WEB-INF/views/")
.build();
}
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return JspView.of("hello.jsp").attr("name", name);
}
}
GET /app/hello/world returns Hello, world! — JspViewRenderer intercepts the
JspView return, copies the name attribute onto the request, and forwards to the JSP
engine at /WEB-INF/views/hello.jsp.
Typed handler integration
To use JspView from a host-class @RestGet without adopting the full mixin, add
JspViewRenderer.class directly to the host's responseProcessors:
@Rest(responseProcessors={JspViewRenderer.class})
public class AppResource extends BasicRestServlet {
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return JspView.of("hello.jsp").attr("name", name);
}
}
The ResponseProcessorList partition pass (added in 10.0.0) automatically repositions
JspViewRenderer to run before SerializedPojoProcessor, so JspView-returning methods
reach the renderer rather than being bean-serialized. The complementary negative case —
a host without the renderer bean-serializes the JspView — is verified by
JspView_TypedHandler_Test#a05.
JSP note: Full template rendering (
${name}substitution) requires a real servlet container with a JSP engine. Under MockRest the dispatcher is absent so the renderer reaches a 500; the invariant tested is that the renderer was reached (response body does not contain"templateName"as a bean-serialized field).
REST resource — raw-JSP mount
The mixin also installs a default /jsp/* mount that forwards raw .jsp requests under
the configured base path. With basePath("/WEB-INF/views/"), a request for
GET /app/jsp/hello.jsp renders /WEB-INF/views/hello.jsp directly — no Java handler
required.
The greedy /* handler is excluded from the generated Swagger / OpenAPI spec via
@OpSwagger(ignore=true) since the path isn't API-meaningful.
Spring Boot caveats
Spring Boot's reference docs explicitly recommend Thymeleaf / FreeMarker / Mustache over JSP for embedded servlet containers — JSP works under embedded Tomcat / Jetty but has notable sharp edges. Plan for the following:
Resource layout — fat-jar vs spring-boot:run
- A Spring Boot fat jar (
spring-boot-maven-pluginrepackaged) does not place.jspfiles where the embedded JSP engine expects them by default. Put.jspresources undersrc/main/resources/META-INF/resources/WEB-INF/views/.... spring-boot:run(Maven plugin) reads fromsrc/main/resources/andsrc/main/webapp/directly; the deployed jar reads from the classpath viaMETA-INF/resources/.- Both modes must work. The
META-INF/resources/WEB-INF/views/layout is the canonical layout that works under both — use it everywhere.
@Bean registration
@Configuration
public class AppConfig {
@Bean
public JspMixin jsp() {
return JspMixin.create()
.basePath("/WEB-INF/views/")
.build();
}
}
The Spring BeanStore adapter resolves the bean through
ApplicationContext.getBean(JspMixin.class); no additional plumbing is required.
@SpringBootTest notes
End-to-end integration tests for the bridge module under embedded Tomcat surface two known constraints that aren't bridge-module bugs but worth documenting up front:
- Servlet-mapping conflicts. Juneau's
RestServletis mapped at/*, which can interceptRequestDispatcher.forward(...)calls intended for the container's JSP servlet. Register the JSP servlet at*.jspwith higher precedence, or mount Juneau at a sub-path such as/api/*.
Response-processor ordering (previously a known limitation, resolved in 10.0.0):
JspViewRenderer now implements
ViewRenderer and is
automatically positioned before SerializedPojoProcessor by the ResponseProcessorList
partition pass. A @RestOp method returning JspView reaches the renderer without any
extra configuration — JspMixin declares
@Rest(mergeResponseProcessorsIntoHost=true), so a plain @Rest(mixins=JspMixin.class) folds
JspViewRenderer into the host's own chain and the framework ensures ordering. See
REST Server — Mixin Sub-Contexts for the opt-in model.
Multiple base paths
Some apps want /views/ for the public site and /admin/views/ for the admin console.
Register two JspMixin beans, each in its own subclass with its own paths
override:
@Rest(paths={"/views/*"})
public class PublicViewsResource extends JspMixin {
public PublicViewsResource() {
super(JspMixin.create().basePath("/WEB-INF/views/public/"));
}
}
@Rest(paths={"/admin/views/*"})
public class AdminViewsResource extends JspMixin {
public AdminViewsResource() {
super(JspMixin.create().basePath("/WEB-INF/views/admin/"));
}
}
Both subclasses mount independently and each resolves templates against its own
basePath.
View-mixin builder contract
JspMixin.Builder implements
ViewMixinBuilder, the
minimal contract shared by all four view-engine mixin builders (basePath(String) +
cacheTemplates(boolean)):
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
as a sibling
juneau-rest-server-view-jsfmodule. - Custom tag libraries beyond JSTL — users supply their own taglibs via classpath dependencies; the bridge module doesn't bundle them.
- Server-side includes (SSI) — separate concern, not commonly mixed with JSP.
- Pre-compiled JSP classfile loading — bypassing the JSP engine entirely is a performance optimization deferred to a future release.
RequestDispatcher.forward()resets the response output stream. A@RestPreCall-hook that has already written response headers/body before the renderer runs may break the forward. The renderer runs at response-resolution time so this is rarely hit by accident; document it for any caller relying onRestPreCall.
See also
- REST Server — Composition (mixins, paths)
- REST Server — Static-Files Mixin
- Thymeleaf View Support — sibling bridge for Thymeleaf
- Mustache View Support — sibling bridge for Mustache
- FreeMarker View Support — sibling bridge for FreeMarker
- Response Processors
- 10.0.0 release notes —
juneau-rest-server-view-jsp(new module)