1. Overview

When building Web Applications, JavaServer Pages (JSP) is one option we can use as a templating mechanism for our HTML pages.

On the other hand, Spring Boot is a popular framework we can use to bootstrap our Web Application.

In this tutorial, we are going to see how we can use JSP together with Spring Boot to build a web application.

First, we’ll see how to set up our application to work in different deployment scenarios. Then we’ll look at some common usages of JSP. Finally, we’ll explore the various options we have when packaging our application.

A quick side note here is that JSP has limitations on its own and even more so when combined with Spring Boot. So, we should consider Thymeleaf or FreeMarker as better alternatives to JSP.

2. Maven Dependencies

Let’s see what dependencies we need to support Spring Boot with JSP.

We’ll also note the subtleties between running our application as a standalone application and running in a web container.

2.1. Running as a Standalone Application

First of all, let’s include the spring-boot-starter-web dependency.

This dependency provides all the core requirements to get a web application running with Spring Boot along with a default Embedded Tomcat Servlet Container:

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

Check out our article Comparing Embedded Servlet Containers in Spring Boot for more information on how to configure an Embedded Servlet Container other than Tomcat.

We should take special note that Undertow does not support JSP when used as an Embedded Servlet Container.

Next, we need to include the tomcat-embed-jasper dependency to allow our application to compile and render JSP pages:

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>9.0.44</version>
</dependency>

While the above two dependencies can be provided manually, it’s usually better to let Spring Boot manage these dependency versions while we simply manage the Spring Boot version.

This version management can be done either by using the Spring Boot parent POM, as shown in our article Spring Boot Tutorial – Bootstrap a Simple Application, or by using dependency management as shown in our article Spring Boot Dependency Management With a Custom Parent.

Finally, we need to include the jstl library, which will provide the JSTL tags support required in our JSP pages:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

2.2. Running in a Web Container (Tomcat)

We still need the above dependencies when running in a Tomcat web container.

However, to avoid dependencies provided by our application clashing with the ones provided by the Tomcat runtime, we need to set two dependencies with provided scope:

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>9.0.44</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.4.4</version>
    <scope>provided</scope>
</dependency>

Note that we had to explicitly define spring-boot-starter-tomcat and mark it with the provided scope. This is because it was already a transitive dependency provided by spring-boot-starter-web.

3. View Resolver Configuration

As per convention, we place our JSP files in the ${project.basedir}/main/webapp/WEB-INF/jsp/ directory.

We need to let Spring know where to locate these JSP files by configuring two properties in the application.properties file:

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

When compiled, Maven will ensure that the resulting WAR file will have the above jsp directory placed inside the WEB-INF directory, which will then be served by our application.

4. Bootstrapping Our Application

Our main application class will be affected by whether we are planning to run as a standalone application or in a web container.

When running as a standalone application, our application class will be a simple @SpringBootApplication annotated class along with the main method:

@SpringBootApplication(scanBasePackages = "com.baeldung.boot.jsp")
public class SpringBootJspApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJspApplication.class);
    }
}

However, if we need to deploy in a web container, we need to extend SpringBootServletInitializer.

This binds our application’s Servlet, Filter and ServletContextInitializer to the runtime server, which is necessary for our application to run:

@SpringBootApplication(scanBasePackages = "com.baeldung.boot.jsp")
public class SpringBootJspApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SpringBootJspApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJspApplication.class);
    }
}

5. Serving a Simple Web Page

JSP pages rely on the JavaServer Pages Standard Tag Library (JSTL) to provide common templating features like branching, iterating and formatting, and it even provides a set of predefined functions.

Let’s create a simple web page that shows a list of books saved in our application.

Say we have a BookService that helps us look up all Book objects:

public class Book {
    private String isbn;
    private String name;
    private String author;

    //getters, setters, constructors and toString
}

public interface BookService {
    Collection<Book> getBooks();
    Book addBook(Book book);
}

We can write a Spring MVC Controller to expose this as a web page:

@Controller
@RequestMapping("/book")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping("/viewBooks")
    public String viewBooks(Model model) {
        model.addAttribute("books", bookService.getBooks());
        return "view-books";
    }
}

Notice above that the BookController will return a view template called view-books. According to our previous configuration in application.properties, Spring MVC will look for view-books.jsp inside the /WEB-INF/jsp/ directory.

We’ll need to create this file in that location:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
    <head>
        <title>View Books</title>
        <link href="<c:url value="/css/common.css"/>" rel="stylesheet" type="text/css">
    </head>
    <body>
        <table>
            <thead>
                <tr>
                    <th>ISBN</th>
                    <th>Name</th>
                    <th>Author</th>
                </tr>
            </thead>
            <tbody>
                <c:forEach items="${books}" var="book">
                    <tr>
                        <td>${book.isbn}</td>
                        <td>${book.name}</td>
                        <td>${book.author}</td>
                    </tr>
                </c:forEach>
            </tbody>
        </table>
    </body>
</html>

The above example shows us how to use the JSTL <c:url> tag to link to external resources such as JavaScript and CSS. We normally place these under the ${project.basedir}/main/resources/static/ directory.

We can also see how the JSTL <c:forEach> tag can be used to iterate over the books model attribute provided by our BookController.

6. Handling Form Submissions

Let’s now see how we can handle form submissions with JSP.

Our BookController will need to provide MVC endpoints to serve the form to add books and to handle the form submission:

public class BookController {

    //already existing code

    @GetMapping("/addBook")
    public String addBookView(Model model) {
        model.addAttribute("book", new Book());
        return "add-book";
    }

    @PostMapping("/addBook")
    public RedirectView addBook(@ModelAttribute("book") Book book, RedirectAttributes redirectAttributes) {
        final RedirectView redirectView = new RedirectView("/book/addBook", true);
        Book savedBook = bookService.addBook(book);
        redirectAttributes.addFlashAttribute("savedBook", savedBook);
        redirectAttributes.addFlashAttribute("addBookSuccess", true);
        return redirectView;
    } 
}

We’ll create the following add-book.jsp file (remember to place it in the proper directory):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Add Book</title>
    </head>
    <body>
        <c:if test="${addBookSuccess}">
            <div>Successfully added Book with ISBN: ${savedBook.isbn}</div>
        </c:if>
    
        <c:url var="add_book_url" value="/book/addBook"/>
        <form:form action="${add_book_url}" method="post" modelAttribute="book">
            <form:label path="isbn">ISBN: </form:label> <form:input type="text" path="isbn"/>
            <form:label path="name">Book Name: </form:label> <form:input type="text" path="name"/>
            <form:label path="author">Author Name: </form:label> <form:input path="author"/>
            <input type="submit" value="submit"/>
        </form:form>
    </body>
</html>

We use the modelAttribute parameter provided by the form:form tag to bind the book attribute added in the addBookView() method in BookController to the form, which in turn will be filled when submitting the form.

As a result of using this tag, we need to define the form action URL separately since we can’t put tags inside tags. We also use the path attribute found in the form:input tag to bind each input field to an attribute in the Book object.

Please see our article Getting Started With Forms in Spring MVC for more details on how to handle form submissions.

7. Handling Errors

Due to the existing limitations on using Spring Boot with JSP, we can’t provide a custom error.html to customize the default /error mapping. Instead, we need to create custom error pages to handle different errors.

7.1. Static Error Pages

We can provide a static error page if we want to display a custom error page for different HTTP errors.

Let’s say we need to provide an error page for all 4xx errors thrown by our application. We can simply place a file called 4xx.html under the ${project.basedir}/main/resources/static/error/ directory.

If our application throws a 4xx HTTP error, Spring will resolve this error and return the provided 4xx.html page.

7.2. Dynamic Error Pages

There are multiple ways we can handle exceptions to provide a customized error page along with contextualized information. Let’s see how Spring MVC provides this support for us using the @ControllerAdvice and @ExceptionHandler annotations.

Let’s say our application defines a DuplicateBookException:

public class DuplicateBookException extends RuntimeException {
    private final Book book;

    public DuplicateBookException(Book book) {
        this.book = book;
    }

    // getter methods
}

Also, let’s say our BookServiceImpl class will throw the above DuplicateBookException if we attempt to add two books with the same ISBN:

@Service
public class BookServiceImpl implements BookService {

    private final BookRepository bookRepository;

    // constructors, other override methods

    @Override
    public Book addBook(Book book) {
        final Optional<BookData> existingBook = bookRepository.findById(book.getIsbn());
        if (existingBook.isPresent()) {
            throw new DuplicateBookException(book);
        }

        final BookData savedBook = bookRepository.add(convertBook(book));
        return convertBookData(savedBook);
    }

    // conversion logic
}

Our LibraryControllerAdvice class will then define what errors we want to handle, along with how we’re going to handle each error:

@ControllerAdvice
public class LibraryControllerAdvice {

    @ExceptionHandler(value = DuplicateBookException.class)
    public ModelAndView duplicateBookException(DuplicateBookException e) {
        final ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("ref", e.getBook().getIsbn());
        modelAndView.addObject("object", e.getBook());
        modelAndView.addObject("message", "Cannot add an already existing book");
        modelAndView.setViewName("error-book");
        return modelAndView;
    }
}

We need to define the error-book.jsp file so that the above error will be resolved here. Make sure to place this under the ${project.basedir}/main/webapp/WEB-INF/jsp/ directory since this is no longer a static HTML but a JSP template that needs to be compiled.

8. Creating an Executable

If we’re planning to deploy our application in a Web Container such as Tomcat, the choice is straightforward, and we’ll use war packaging to achieve this.

However, we should be mindful that we can’t use jar packaging if we are using JSP and Spring Boot with an Embedded Servlet Container. So, our only option is war packaging if running as a Standalone Application.

Our pom.xml will then, in either case, need to have its packaging directive set to war:

<packaging>war</packaging>

In case we didn’t use the Spring Boot parent POM for managing dependencies, we’ll need to include the spring-boot-maven-plugin to ensure that the resulting war file is capable of running as a Standalone Application.

We can now run our standalone application with an Embedded Servlet Container or simply drop the resulting war file into Tomcat and let it serve our application.

9. Conclusion

We’ve touched on various topics in this tutorial. Let’s recap some key considerations:

  • JSP contains some inherent limitations. Consider Thymeleaf or FreeMarker instead.
  • Remember to mark necessary dependencies as provided if deploying on a Web Container.
  • Undertow will not support JSP if used as an Embedded Servlet Container.
  • If deploying in a web container, our @SpringBootApplication annotated class should extend SpringBootServletInitializer and provide necessary configuration options.
  • We can’t override the default /error page with JSP. Instead, we need to provide custom error pages.
  • JAR packaging is not an option if we are using JSP with Spring Boot.

As always, the full source code with our examples is available over on GitHub.