Spring BootJSP Integration
Spring Boot

JSP Integration

JavaServer Pages (JSP) is the original Java view technology, predating Spring MVC. Spring Boot supports JSP but with significant limitations — it does not work with the default executable JAR packaging and requires embedded Tomcat Jasper. For new projects Thymeleaf or FreeMarker are strongly preferred. This entry covers JSP setup, configuration, and its limitations in Spring Boot.

JSP Limitations in Spring Boot

Spring Boot's embedded container support for JSP is limited by design. Understanding the constraints before starting is important — they affect how the application is packaged and deployed.
Shell
# ── Known limitations: ───────────────────────────────────────────────

# 1. No executable JAR support
#    JSPs are compiled by the servlet container's Jasper engine at runtime.
#    The embedded Tomcat JAR (spring-boot-starter-tomcat) does not include
#    Jasper. You must either:
#    a) Add tomcat-embed-jasper (provided scope) and run as WAR, OR
#    b) Use mvn spring-boot:run (works) but NOT java -jar myapp.jar

# 2. Undertow and Jetty do not support JSP
#    Only embedded Tomcat supports JSP. Undertow has no JSP support;
#    Jetty requires separate configuration.

# 3. Whitelabel error page does not use JSP
#    Spring Boot's error handling does not resolve JSP error pages
#    from /WEB-INF/views/error/*.jsp automatically.

# 4. No hot-reload
#    Unlike Thymeleaf (cache: false), JSP changes require a restart
#    unless you configure JRebel or Spring DevTools carefully.

# ── When JSP is acceptable: ───────────────────────────────────────────
# - Migrating a legacy Spring MVC application to Spring Boot
# - Team has deep JSP expertise and no budget to migrate
# - Deploying as WAR to an external Tomcat (standard enterprise setup)

# ── Recommended alternatives: ────────────────────────────────────────
# - Thymeleaf   → spring-boot-starter-thymeleaf  (recommended)
# - FreeMarker  → spring-boot-starter-freemarker
# - Mustache    → spring-boot-starter-mustache

JSP Setup and Dependencies

To use JSP in Spring Boot you need three things: the Jasper engine dependency, the JSTL dependency, and the WAR packaging plugin. The project must be packaged as WAR and extend SpringBootServletInitializer.
XML
<!-- pom.xml — packaging must be war: -->
<packaging>war</packaging>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Jasper — JSP compiler (provided: included in external Tomcat): -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- JSTL — tag library for JSP: -->
    <dependency>
        <groupId>jakarta.servlet.jsp.jstl</groupId>
        <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>jakarta.servlet.jsp.jstl</artifactId>
    </dependency>
</dependencies>

# ── application.yml: ──────────────────────────────────────────────────
spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp

# ── File layout: ──────────────────────────────────────────────────────
# src/main/
# ├── java/
# │   └── com/example/
# │       ├── MyApplication.java
# │       └── ServletInitializer.java   ← required for WAR deployment
# └── webapp/
#     └── WEB-INF/
#         └── views/
#             ├── users/
#             │   ├── list.jsp
#             │   └── form.jsp
#             └── error/
#                 └── 404.jsp

ServletInitializer and Application Class

WAR deployment requires a SpringBootServletInitializer subclass. Spring Boot's WAR archetype generates this class automatically. The main application class remains unchanged.
Java
// ── Main application class (unchanged): ──────────────────────────────
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// ── ServletInitializer — required for WAR / external Tomcat: ──────────
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder application) {
        return application.sources(MyApplication.class);
    }
}

// ── InternalResourceViewResolver (auto-configured from application.yml,
//    or declare explicitly): ────────────────────────────────────────────
@Configuration
public class MvcConfig {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver =
            new InternalResourceViewResolver();
        resolver.setViewClass(JstlView.class);   // enables JSTL tag support
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

JSP Templates with JSTL

JSP templates use JSTL (JavaServer Pages Standard Tag Library) for iteration, conditionals, and URL building. Model attributes set in the controller are available as EL (Expression Language) variables.
jsp
<%-- src/main/webapp/WEB-INF/views/users/list.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn"  uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
    <title>Users</title>
    <link rel="stylesheet" href="<c:url value='/static/css/main.css'/>">
</head>
<body>

<%-- EL — access model attributes by name: --%>
<h1>${pageTitle}</h1>

<%-- c:if — conditional rendering: --%>
<c:if test="${empty users}">
    <p>No users found.</p>
</c:if>

<%-- c:forEach — iterate over a collection: --%>
<c:if test="${not empty users}">
<table>
    <tr>
        <th>Name</th><th>Email</th><th>Created</th><th>Actions</th>
    <tr>
    <c:forEach items="${users}" var="user" varStatus="status">
    <tr class="${status.index % 2 == 0 ? 'even' : 'odd'}">
        <td>${user.name}</td>
        <td>${user.email}</td>
        <td>
            <%-- fmt:formatDate — format java.util.Date: --%>
            <fmt:formatDate value="${user.createdAt}" pattern="dd MMM yyyy" />
         </td>
        <td>
            <a href="<c:url value='/users/${user.id}'/>">View</a>
            <a href="<c:url value='/users/${user.id}/edit'/>">Edit</a>
         </td>
    </tr>
    </c:forEach>
</table>
</c:if>

<%-- c:choose / c:when / c:otherwise: --%>
<c:choose>
    <c:when test="${user.role == 'ADMIN'}">
        <span class="badge-red">Admin</span>
    </c:when>
    <c:when test="${user.role == 'USER'}">
        <span class="badge-blue">User</span>
    </c:when>
    <c:otherwise>
        <span class="badge-gray">Unknown</span>
    </c:otherwise>
</c:choose>

<%-- c:url — build URLs (handles context path): --%>
<a href="<c:url value='/users/new'/>">Add User</a>
<img src="<c:url value='/static/images/logo.png'/>" />

<%-- fn: — JSTL functions: --%>
<p>Total: ${fn:length(users)} users</p>

</body>
</html>

JSP Form Handling

JSP form submission uses standard HTML form elements. Spring MVC's @ModelAttribute binds the submitted fields to a Java object exactly as with Thymeleaf. Spring's form tag library provides helpers for pre-populating fields and displaying errors.
jsp
<%-- Add Spring form tag library: --%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c"    uri="http://java.sun.com/jsp/jstl/core" %>

<%-- Spring form:form — binds to the modelAttribute object: --%>
<form:form method="post" action="/users" modelAttribute="userForm">

    <div>
        <label for="name">Name</label>
        <%-- form:input — pre-populates value from the model object: --%>
        <form:input path="name" id="name" cssClass="form-control"
                    cssErrorClass="form-control is-invalid" />
        <%-- form:errors — displays validation errors for the field: --%>
        <form:errors path="name" cssClass="invalid-feedback" element="div" />
    </div>

    <div>
        <label for="email">Email</label>
        <form:input path="email" type="email" id="email" />
        <form:errors path="email" cssClass="error" element="span" />
    </div>

    <div>
        <label for="role">Role</label>
        <%-- form:select — renders <select> bound to enum field: --%>
        <form:select path="role" id="role">
            <form:option value="">-- Select --</form:option>
            <form:options items="${roles}" />
        </form:select>
        <form:errors path="role" element="span" />
    </div>

    <div>
        <label>
            <%-- form:checkbox — binds to boolean field: --%>
            <form:checkbox path="active" /> Active
        </label>
    </div>

    <%-- Global (non-field) errors: --%>
    <form:errors path="*" element="div" cssClass="alert alert-danger" />

    <button type="submit">Save</button>
    <a href="<c:url value='/users'/>">Cancel</a>

</form:form>