Skip to main contentLogo

Command Palette

Search for a command to run...

JSP — Java Server Pages

Published on
Apr 13, 2025
JSP — Java Server Pages

JSP - JavaServer Pages

In the Java ecosystem, when talking about building web applications, JSP (JavaServer Pages) is inevitable. JSP is a server-side technology that allows embedding Java code into HTML, XML, or other markup languages to create dynamic web pages. Its main goal is to help separate the presentation layer from business logic, although how effective this separation is depends greatly on how you use JSP.


Compared to servlets, JSP provides a more comfortable environment for frontend developers or designers, because it mostly resembles HTML and dynamic parts are added with special tags or minimal Java code. However, understanding what happens behind the scenes is crucial for any serious Java web developer.

What Happens Behind the Scenes of JSP: The Lifecycle

When a JSP page receives a request for the first time or when the web application starts (depending on configuration), it goes through a lifecycle. Understanding this process is critical to grasp how JSP works and potential pitfalls:

  1. Translation Phase: The JSP container (e.g., Jasper inside Tomcat or Jetty) reads the .jsp file and converts it into an equivalent Java Servlet source code file (.java). During this phase, all JSP elements (scriptlets, expressions, directives, actions) are transformed into appropriate Java code. Static HTML/markup is converted into statements like out.println().
  2. Compilation Phase: The JSP container compiles the generated .java file into Java bytecode (.class) using the standard Java compiler (javac). Any syntax errors will surface at this stage.
  3. Loading Phase: The compiled .class file is loaded into the JVM (Java Virtual Machine).
  4. Instantiation Phase: An instance (object) is created from the loaded class. Typically, only one instance is created per JSP page (though different configurations are possible).
  5. Initialization Phase: As in the Servlet specification, the container calls the jspInit() method. This method is called only once, right after the instance is created, and can be used for tasks like opening database connections or other one-time setups. You can override it using the JSP declaration element.
  6. Request Processing Phase: For each client request, the container creates a new thread and calls the _jspService() method on that thread. All your scriptlet code, expressions, and HTML in the JSP file are placed inside this method. This method accepts the request and response objects as parameters.
    • ⚠️ Important: Since the _jspService() method runs in a multithreaded environment, be careful with thread safety when using instance variables (those created with declarations). Local variables (created inside scriptlets) are per-request and thus thread-safe.
  7. Destruction Phase: When the web application stops or the JSP container is shut down, the container calls the jspDestroy() method. This is also called only once and can be used to close resources opened in jspInit() (e.g., database connections).

➡️ Key Takeaway: Every JSP file is actually a Servlet running behind the scenes! Understanding this is fundamental to comprehending JSP behavior and its relationship with the Servlet context.

JSP Syntax

A JSP page consists of a mix of static markup (HTML, XML) and dynamic JSP elements. The JSP container processes these elements and sends the result to the client.

Scripting Elements (The "Old" Way — Use with Caution!)

These elements allow embedding raw Java code directly into JSP pages. In modern JSP practices (with EL and JSTL), it is recommended to avoid them as much as possible because they mix presentation with logic and make the code unreadable. Still, it’s important to know them:

  • Declarations (<%! ... %>):

    • Used to declare variables (member variables) and methods at the generated servlet class level.
    • Variables declared here are shared across all requests and are not thread-safe! Use with great caution.
    • More commonly used for declaring methods (e.g., helper methods or overriding jspInit/jspDestroy).
    example/Declarations.jsp
    <%!
        private int counter = 0; // WARNING: Not thread-safe if modified concurrently!
        private String formatData(String data) {
            // Some formatting logic
            return "[" + data + "]";
        }
        public void jspInit() {
            log("My JSP is initializing!");
        }
    %>
  • Scriptlets (<% ... %>):

    • Java code blocks that will be placed inside the _jspService() method of the generated servlet.
    • Any amount of Java code (variable declarations, loops, conditionals, method calls) can be written here.
    • Local variables declared here are recreated for each request and are thread-safe.
    • ⚠️ Main Problem: Mixing HTML markup with Java code (spaghetti code) severely reduces readability and maintainability. Avoid this!
    example/Scriptlet.jsp
    <%
        String userName = request.getParameter("user");
        if (userName != null && !userName.isEmpty()) {
            for (int i = 0; i < 3; i++) {
                out.println("Hello, " + userName + "! (" + i + ")<br>");
            }
        } else {
            out.println("Hello, Guest!<br>");
        }
        // Accessing a declared method:
        out.println(formatData("Some data"));
    %>
  • Expressions (<%= ... %>):

    • Evaluates a Java expression, converts it to a String, and writes it directly to the response output stream (like out.print(...)).
    • Do not put a semicolon (;) at the end of the code block.
    • More concise and readable than using out.println() in scriptlets.
    HTML5
    example/Expressions.jsp
    <p>Current time is: <%= new java.util.Date() %></p>
    <p>Welcome, <%= request.getParameter("user") != null ? request.getParameter("user") : "Guest" %></p>
    <p>Formatted data: <%= formatData("example") %></p>

Directives (<%@ ... %>)

Directives provide instructions to the JSP container during the translation phase on how to process the page. They do not produce output at runtime.

  • page Directive: The most commonly used directive; it sets various page-level attributes. A page may contain multiple page directives, but some attributes (e.g., language) can be set only once. Key attributes:

    • import="package.Class, package.*, ...": Imports Java classes. Multiple imports are comma-separated. Packages like java.lang.*, javax.servlet.*, javax.servlet.http.*, and javax.servlet.jsp.* are imported automatically.
    • contentType="mimeType; charset=encoding": Sets the MIME type and character encoding of the response sent (e.g., "text/html; charset=UTF-8"). Proper encoding (UTF-8) is essential for Azerbaijani characters.
    • session="true|false": Determines whether this page participates in HTTP sessions. Default is true. If no session is needed, setting false can improve performance. When false, the implicit object session is unavailable.
    • errorPage="relativeURL": Specifies the page to which the request will be forwarded when an uncaught exception occurs on the page.
    • isErrorPage="true|false": Indicates whether this page is an error page that receives errors forwarded from another page. Default is false. When true, the implicit object exception is available.
    • buffer="none|sizekb": Sets the buffer size for the output stream (out object). Default is usually 8kb. none disables buffering (may impact performance negatively).
    • autoFlush="true|false": Determines whether the buffer is automatically flushed to the client when full. Default is true. If false and the buffer fills, an exception will occur.
    • language="java": Specifies the scripting language used. Currently only "java" is supported and is the default.
    directives/page-directive.jsp
    <%@ page contentType="text/html; charset=UTF-8"
             import="java.util.ArrayList, com.example.model.User"
             session="true"
             errorPage="/WEB-INF/jsp/error.jsp" %>
  • include Directive: Statically includes the contents (text, HTML, JSP code) of another file into the current JSP page during the translation phase. The included file is processed as if it were part of the main file. The path must be relative.

    • Difference: Unlike the <jsp:include> action, this directive includes the file only once before compilation. If the included file changes, the main JSP file must be recompiled.
    • Typically used with .jspf (JSP Fragment) files for static or reusable code parts like headers and footers.
    directives/include-directive.jsp
    <%@ include file="/WEB-INF/jspf/header.jspf" %>
    <h1>Main Content</h1>
    <%@ include file="/WEB-INF/jspf/footer.jspf" %>
  • taglib Directive: Includes a custom tag library (including JSTL) in the page. The prefix attribute defines the namespace used when referencing the tags, and uri indicates the location of the Tag Library Descriptor (TLD) file or a known URI.

    directives/taglib-directives.jsp
    <%@ taglib prefix="c" uri="[<http://java.sun.com/jsp/jstl/core>](<http://java.sun.com/jsp/jstl/core>)" %>
    <%@ taglib prefix="fmt" uri="[<http://java.sun.com/jsp/jstl/fmt>](<http://java.sun.com/jsp/jstl/fmt>)" %>
    <%@ taglib prefix="custom" uri="/WEB-INF/tlds/custom.tld" %>

    (We’ll cover JSTL and custom tags in detail later.)

JSP Implicit Objects

For each JSP page (more precisely, for its _jspService method), the JSP container automatically makes a number of objects available. These objects can be used directly within scriptlets and expressions.

  • request (javax.servlet.http.HttpServletRequest): Represents the current HTTP request. Used to access parameters, headers, cookies, attributes, and the session.
  • response (javax.servlet.http.HttpServletResponse): Represents the current HTTP response. Used to set headers, add cookies, and redirect. Although response.getWriter() can be used to write directly to the output stream, the out object is usually more convenient.
  • out (javax.servlet.jsp.JspWriter): A buffered writer object used to write content to the response body. Methods include println(), print(), and newLine().
  • session (javax.servlet.http.HttpSession): Represents the session object for the current client. Used to store user data across requests (setAttribute, getAttribute). Available only when <%@ page session="true" %> (default).
  • application (javax.servlet.ServletContext): Represents the context for the entire web application. Used to store data shared across all users and requests (application scope attributes), initialization parameters, and to access resources.
  • config (javax.servlet.ServletConfig): Stores configuration information for the current JSP page (generated servlet), such as init parameters defined in web.xml.
  • pageContext (javax.servlet.jsp.PageContext): A central object that provides programmatic access to all other implicit objects and various scopes (page, request, session, application). It also holds methods for attribute management, request forwarding, and including. Heavily used within custom tag handlers.
  • page (java.lang.Object): Represents the instance of the current JSP page (this reference). Rarely used directly.
  • exception (java.lang.Throwable): Available only on error pages (<%@ page isErrorPage="true" %>) and holds the exception that occurred on the original page.

JSP Standard Actions (<jsp:...>)

Standard actions provide XML-based alternatives to scripting elements. They are more readable, better separate presentation logic, and are managed by the container.

  • <jsp:useBean>: Used to manage JavaBeans components. Either finds an existing bean in a specific scope or creates a new one.

    • id: Variable name used to reference the bean within the JSP page.
    • class: Fully-qualified class name of the bean (if a new instance should be created).
    • scope: The scope in which the bean is stored and searched. Values: page (default), request, session, application. The container first searches for the bean by id in the specified scope; if not found, it creates a new one (if the class attribute is provided) and stores it in that scope.
    actions/useBean.jsp
    <jsp:useBean id="user" class="com.example.model.User" scope="session" />
  • <jsp:setProperty>: Used to set the properties of a bean found or created with <jsp:useBean>.

    • name: The id of the target bean as defined in <jsp:useBean>.
    • property: The name of the property to set. The bean must have a matching setter (e.g., setPropertyName()).
    • property="*": Special value. Attempts to automatically set all matching properties by mapping request parameter names to bean property names (type conversion is automatic). Very powerful, but use with caution.
    • value: A fixed value (String or expression) to set for the property.
    • param: The request parameter from which to get the value for the property. If param and value are not provided, the value is taken from a request parameter with the same name as the property.
    actions/setProperty.jsp
    <jsp:useBean id="product" class="com.example.model.Product" scope="request" />
    
    <%-- Set specific property from a request parameter named 'productName' --%>
    <jsp:setProperty name="product" property="name" param="productName" />
    
    <%-- Set specific property with a fixed value --%>
    <jsp:setProperty name="product" property="category" value="Electronics" />
    
    <%-- Set all matching properties from request parameters --%>
    <jsp:setProperty name="product" property="*" />
  • <jsp:getProperty>: Retrieves a bean property and writes it to the output (calls the getter). Rarely used, since EL (Expression Language) is more convenient.

    • name: The bean id.
    • property: The name of the property to retrieve.
    actions/getProperty.jsp
    Product Name: <jsp:getProperty name="product" property="name" /><br/>
    Price: <jsp:getProperty name="product" property="price" />
  • <jsp:include>: Dynamically includes another resource (JSP, Servlet, HTML) into the current page at request time. The included resource is processed separately and its output is appended to the main page’s output.

    • page: The relative URL of the resource to include. Can be a runtime expression.
    • flush="true|false": Indicates whether to flush the main page’s buffer before including. Default is false, but true may be more stable in some containers.
    • Difference: Unlike the <%@ include ... %> directive, this action runs on every request and includes the latest version of the resource, providing more dynamism.
    • Parameters can be passed via <jsp:param>.
    actions/include.jsp
    <p>Including dynamic content:</p>
    <jsp:include page="/jsp/dynamicMenu.jsp">
        <jsp:param name="userRole" value="${currentUser.role}" />
    </jsp:include>
  • <jsp:forward>: Forwards the current request to another resource (JSP, Servlet, HTML). The forwarding occurs entirely on the server-side; the client browser is unaware (URL does not change). The current page’s output buffer is cleared, and execution stops where the forward occurs.

    • page: The relative URL of the resource to forward to. Can be a runtime expression.
    • Parameters can be added using <jsp:param> (appended to the original request parameters).
    • Primarily used in MVC (Model-View-Controller) architectures to forward from the Controller (Servlet) to the View (JSP).
    actions/forward.jsp
    <%-- Forwarding example (less common to do this FROM a JSP) --%>
    <jsp:forward page="/WEB-INF/jsp/detailedView.jsp">
        <jsp:param name="productId" value="123" />
    </jsp:forward>
    <%-- Code below this line will NOT be executed --%>

<jsp:param>

Used within <jsp:include> and <jsp:forward> actions to add request parameters to the included or forwarded resource.

  • name: Parameter name.
  • value: Parameter value.

Expression Language (EL): Simplifying JSP

To avoid the mess created by scriptlets, Expression Language (EL) was introduced with JSP 2.0. EL is designed to provide easy and readable access from JSP pages to JavaBeans properties, collections, implicit objects, and request parameters. The syntax is ${expression}. EL is the foundation of modern JSP development.

  • Basic Syntax: ${...}

  • JavaBeans Property Access: ${beanName.propertyName} (Under the hood, calls getBeanName().getPropertyName()).

  • Collection/Array Access: ${myList[0]}, ${myMap.keyName}, ${myMap['key-with-hyphen']}.

  • Implicit Objects (for EL):

    • pageScope, requestScope, sessionScope, applicationScope: Directly access attributes in the respective scope (e.g., ${requestScope.user}). If the scope is not specified (e.g., ${user}), EL automatically searches the scopes in the order: page, request, session, application.
    • param: Access request parameters (e.g., ${param.userId}). Equivalent to request.getParameter().
    • paramValues: Retrieve multiple request parameters with the same name as a String array (e.g., ${paramValues.interests[0]}). Equivalent to request.getParameterValues().
    • header: Access HTTP request headers (e.g., ${header['User-Agent']}).
    • headerValues: Retrieve multiple headers with the same name as a String array.
    • cookie: Access cookies (e.g., ${cookie.userPref.value}). Returns the cookie object with the given name.
    • initParam: Access context initialization parameters defined in the deployment descriptor (web.xml) (e.g., ${initParam.adminEmail}). Equivalent to servletContext.getInitParameter().
    • pageContext: Access the PageContext object itself (e.g., ${pageContext.request.contextPath} to get the context path).
  • Operators:

    • Arithmetic: +, , , / (or div), % (or mod).
    • Logical: && (or and), || (or or), ! (or not).
    • Relational: == (or eq), != (or ne), < (or lt), > (or gt), <= (or le), >= (or ge).
    • empty: Checks whether a value is null, an empty String, an empty collection, or an empty map (e.g., ${empty param.search}).
    • Conditional: A ? B : C (e.g., ${user.isAdmin ? 'Admin' : 'User'}).
  • Examples:

    views/el-examples.jsp
    <p>User Name: ${sessionScope.loggedInUser.name}</p> <%-- Accessing bean in session scope --%>
    <p>Product ID from URL: ${param.productId}</p> <%-- Accessing request parameter --%>
    <p>Shopping Cart is empty: ${empty shoppingCart.items}</p> <%-- Using empty operator --%>
    <p>Context Path: ${pageContext.request.contextPath}</p> <%-- Accessing via pageContext --%>
    <p>Total Price: ${order.price * (1 + initParam.taxRate)}</p> <%-- Calculation with init param --%>
  • Ignore EL: Although rarely needed, you can disable EL processing on a page with the directive <%@ page isELIgnored="true" %>.

Servlet and JSP Integration: The MVC Dance (Detailed Explanation)

As mentioned earlier, a JSP is compiled into a Servlet. In real-world applications, however, they typically work together within the Model-View-Controller (MVC) architecture:

  • Model: Objects that hold the application’s data and business logic (JavaBeans, POJOs, Entities).
  • View: The component that presents data to the user (typically JSP + EL + JSTL). The View should contain as little logic as possible.
  • Controller: The component that receives incoming requests, processes user input, updates the Model, and selects and forwards the appropriate View to display the result (typically a Servlet).

Most Common Model: Servlet as Controller, JSP as View

This is the standard and most recommended approach:

  1. Request Arrives: The client sends a request to a URL (e.g., /users?action=list).

  2. Controller (Servlet) Processes: The Deployment Descriptor (web.xml) or the @WebServlet annotation routes this URL pattern to the appropriate servlet.

    • The servlet accepts the request (doGet, doPost methods).
    • Reads request parameters (request.getParameter("action")).
    • Invokes necessary business logic (e.g., obtains the list of users from UserService).
    • Places the retrieved data (Model) into the request scope as an attribute: request.setAttribute("userList", listOfUsers);.
  3. Controller Forwards to the View: The servlet forwards the request to the JSP page that will display the data using RequestDispatcher:

    controller/UsersServlet.java
    // Inside Servlet's doGet/doPost
    List<User> userList = userService.getAllUsers();
    request.setAttribute("userList", userList);
    RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/userList.jsp");
    dispatcher.forward(request, response);
  4. View (JSP) Displays the Data: The JSP page displays the data in the request scope using EL and JSTL:

    HTML5
    WEB-INF/jsp/userList.jsp
    <%-- /WEB-INF/jsp/userList.jsp --%>
    <%@ taglib prefix="c" uri="[<http://java.sun.com/jsp/jstl/core>](<http://java.sun.com/jsp/jstl/core>)" %>
    <html>
    <head><title>User List</title></head>
    <body>
      <h1>Users</h1>
      <table>
        <thead><tr><th>ID</th><th>Name</th><th>Email</th></tr></thead>
        <tbody>
          <c:forEach items="${userList}" var="user">
            <tr>
              <td><c:out value="${user.id}" /></td>
              <td><c:out value="${user.name}" /></td>
              <td><c:out value="${user.email}" /></td>
            </tr>
          </c:forEach>
        </tbody>
      </table>
    </body>
    </html>

➡️ JSPs Under /WEB-INF/: Note that the JSP file (/WEB-INF/jsp/userList.jsp) is located under the /WEB-INF directory. This is an important security practice. Resources under /WEB-INF cannot be accessed directly via a URL from the client browser. They can only be reached via server-side forwarding. This prevents users from bypassing the controller and directly accessing the view, preserving the MVC flow.

Less Common Model: Including a Servlet Inside JSP

With the <jsp:include page="/path/to/servlet" /> action, a JSP page can include the output of another servlet into its content.

  1. Main JSP: Renders the main part of the page.
  2. <jsp:include>: At a certain point, calls <jsp:include page="/dynamicFragmentServlet" />.
  3. Included Servlet: Receives the request (with the same request and response objects as the main JSP), executes its logic, and writes the result to its own response.getWriter().
  4. Result is Combined: The output of the included servlet is appended to the output of the main JSP at that point.

Use Cases: This approach is less common. It is mostly used to have certain dynamic parts of a page (e.g., a personalized ad block, dynamic navigation menu) managed by separate servlets and included into various JSPs. However, it is usually considered cleaner for the controller to prepare all data and pass it to a single JSP. Sometimes JSP Fragments (.jspf) and custom tags are better alternatives.

Forward vs Redirect (From Servlet to JSP)

  • forward (RequestDispatcher.forward()):
    • Happens entirely on the server-side.
    • The browser URL does not change.
    • The original request object (and its attributes) is forwarded to the new resource.
    • Faster (no additional network roundtrip).
    • Commonly used to pass data from the controller to the view.
  • redirect (response.sendRedirect()):
    • The server sends a 302 status code and a new URL to the browser.
    • The browser sends a completely new request to the new URL.
    • The original request objects and attributes are lost. To pass data, use session scope or URL parameters (less efficient).
    • The URL changes in the browser.
    • Post-Redirect-Get (PRG) Pattern: The main use case is to prevent the resubmission of the same POST request on browser refresh after a POST request (e.g., form submission). The controller processes the POST and then redirects to a results page with a GET request.

Advanced Topics and Best Practices

Error Handling

  • Use the directives <%@ page errorPage="error.jsp" %> and <%@ page isErrorPage="true" %> for page-level error handling. Use the exception object on the error page.
  • Define global error pages in web.xml with <error-page> elements for HTTP status codes (404, 500) or exception types. This is a more centralized approach.

Security

  • Avoid Scriptlets: The golden rule! Use EL and JSTL.
  • XSS Prevention: Always escape user-supplied or dynamic data using <c:out> or equivalent escaping mechanisms.
  • Input Validation: Implement both client-side (JavaScript) and (more importantly) server-side (Servlet/Service layer) validation. Never rely solely on client-side validation.
  • /WEB-INF/: Place all JSP views under /WEB-INF.
  • CSRF (Cross-Site Request Forgery) Prevention: Add unique tokens to forms and validate them on the server side.

Performance

  • JSP Compilation: Understand that JSPs are converted to servlets on the first request (or when precompiled). This first request may be slightly slower.
  • Session Management: If a page does not use the session, disable it with <%@ page session="false" %>. This removes unnecessary session creation overhead.
  • include Directive vs Action: For static content (headers, footers), the <%@ include file="..." %> directive is more performant than the <jsp:include> action, as it runs only once during the translation phase.

JSP Fragments (.jspf)

Files with the .jspf extension are usually not full JSP pages; they are intended to be included in other JSPs using the <%@ include ... %> directive. This is useful for reusable static markup or JSP code blocks.

JSP and Modern Web Development

It must be admitted that with the rise of modern Single Page Applications (SPA) (React, Angular, Vue) and microservices architectures, the use of traditional server-rendered JSP has somewhat declined. Often the backend (e.g., Spring Boot, Jakarta EE REST) provides JSON APIs, and the frontend is a separate JavaScript application that consumes these APIs.

However, JSP is still used in many existing Jakarta EE applications, in some new projects (especially when server-side rendering is preferred), and alongside alternative template engines like Thymeleaf.

Conclusion

JSP is an important part of Java web development. Understanding its lifecycle, various elements (scripting, directives, actions), implicit objects, and especially modern approaches like Expression Language (EL) and JSP Standard Tag Library (JSTL) is critical to building effective and maintainable web applications. Proper integration with servlets (especially within the MVC pattern) and security practices like placing views under /WEB-INF/ should not be overlooked.

Avoid scriptlets and focus on EL/JSTL to achieve a cleaner, more readable, and maintainable presentation layer.

Thanks for reading.