Skip to main content

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 View interface itself, see the 10.0.0 release notes under the juneau-rest-server section. 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​

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

ContainerRecommended engineMaven coordinates
Jetty 12 EE11 — juneau-microservice-jetty, Spring Boot embedded JettyEclipse Jetty's repackaged Jasper bundleorg.eclipse.jetty.ee11:jetty-ee11-apache-jsp
Embedded Tomcat — Spring Boot defaultApache Tomcat's embedded Jasperorg.apache.tomcat.embed:tomcat-embed-jasper
External WAR deploy — Tomcat / JBoss / WildFly / etc.Container-suppliedno 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-plugin repackaged) does not place .jsp files where the embedded JSP engine expects them by default. Put .jsp resources under src/main/resources/META-INF/resources/WEB-INF/views/....
  • spring-boot:run (Maven plugin) reads from src/main/resources/ and src/main/webapp/ directly; the deployed jar reads from the classpath via META-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:

  1. Servlet-mapping conflicts. Juneau's RestServlet is mapped at /*, which can intercept RequestDispatcher.forward(...) calls intended for the container's JSP servlet. Register the JSP servlet at *.jsp with 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-jsf module.
  • 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 on RestPreCall.

See also​