eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

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>3.2.2</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>10.1.18</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>10.1.18</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>3.2.2</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.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)