Standard Actions & JavaBeans

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Jakarta Pages 4.0 (chapter 5, "Standard Actions", and sections 1.14 and 1.8 for the rules they depend on), with Jakarta Expression Language 6.0, Jakarta Tags 3.0 and Jakarta Servlet 6.1, running on Apache Tomcat 11.0.26 and Java 21. Everything was verified on 2026-10-08: the facts against the Pages 4.0 specification, the Javadoc of java.beans and the Tomcat 11 documentation, and every example by building it into a Maven WAR and requesting it with curl on Tomcat 11.0.26; the outputs and error messages shown are the ones that run produced, and rows marked "observed" were reproduced. A standard action is an XML-style element with the prefix jsp that every Pages container understands without a taglib directive. Thirteen of them exist in Pages 4.0 (a fourteenth, jsp:plugin, was removed). The three that give the page a model are jsp:useBean, jsp:setProperty and jsp:getProperty; they date from JSP 0.92 and they are the reason the JavaBeans conventions appear in a web guide at all. The others compose pages (jsp:include, jsp:forward, jsp:param), generate markup (jsp:element, jsp:attribute, jsp:body, jsp:text, jsp:output) or belong to tag files (jsp:invoke, jsp:doBody). The running scenario is the Bookshelf of this section: a bean describes a book, a form fills it, a fragment renders it.

The bean tags are legacy in the sense that new code reads and writes beans with EL and JSTL, and forms are bound by a controller. They still matter for three reasons. They are in every JSP written before 2005, which is what most maintenance work meets. They are the simplest demonstration of the page-to-bean contract that Struts, Spring MVC and Jakarta Faces all re-implement. And jsp:include, jsp:forward, jsp:param, jsp:element, jsp:attribute, jsp:body and jsp:text are not legacy at all: they are the supported way to compose pages without a tag library.

The Standard Actions at a Glance

A standard action is written <jsp:name attributes> in standard syntax and in a JSP document alike. A translation error results when the jsp prefix is used for an element that is not a standard action; Tomcat 11.0.26 reports Invalid standard action for jsp:plugin, which Pages 4.0 removed (observed).

Table 1. Pages 4.0 standard actions (chapter 5), where each may appear and its status
Action Purpose Where it may appear Status in Pages 4.0

jsp:useBean

Find a bean in a scope or create it, and name it for the page

Any JSP page, tag file or JSP document

Current, but superseded by EL and a controller in new code

jsp:setProperty

Set bean properties from request parameters, a string or a request-time value

After the jsp:useBean that introduces the bean

Current, legacy style

jsp:getProperty

Write one bean property, converted to a string, to the output

After the jsp:useBean that introduces the bean

Current, legacy style; does not escape

jsp:include

Include a static or dynamic resource at request time

Anywhere template data can appear

Current and idiomatic

jsp:forward

Dispatch the current request to another resource and stop the page

Anywhere template data can appear

Current

jsp:param

A name and value pair for jsp:include or jsp:forward

Only inside those two (a translation error elsewhere)

Current

jsp:attribute

Give an action attribute, or the attribute of a generated element, in an element body

Only as a child of a standard or custom action

Current

jsp:body

Give the body of an action explicitly

As a child of an action, required when jsp:attribute is used

Current

jsp:element

Generate an XML element whose name is computed at request time

JSP pages, tag files and JSP documents

Current

jsp:text

Pass template text through unchanged and without whitespace trimming

Anywhere template data can appear

Current

jsp:output

Control the XML declaration and the DOCTYPE of the output

Only in JSP documents and tag files in XML syntax

Current

jsp:invoke

Invoke a fragment attribute of a tag file

Only in tag files

Current; see Tag Files & Layouts

jsp:doBody

Evaluate the body passed to a tag file

Only in tag files

Current

jsp:plugin, jsp:params, jsp:fallback

Embed an applet or bean in the page

Nowhere

Removed in Pages 4.0 (deprecated in 3.1)

Three attribute-handling rules apply to every action, and explain several error messages later on this page.

  • Attribute values given as plain strings are converted to the attribute’s declared type (section 1.14.2.1, table JSP.1-11). Attribute values given as ${...} or <%= ... %> are request-time attribute values (section 1.14.1) and are evaluated for every request. Only some attributes accept them; the attribute tables below say which.

  • An attribute can be given as an XML attribute or as a jsp:attribute child, never both for the same attribute, and a translation error results if both appear.

  • Action and attribute names are case sensitive: jsp:useBean is not jsp:usebean.

JavaBeans Conventions

A JavaBean is, for the purposes of Pages, an ordinary Java class that follows a few naming rules so that tools (and Jasper) can discover its properties without being told about them. The rules come from the JavaBeans specification and are implemented by java.beans.Introspector; Jasper introspects the bean class for every jsp:setProperty and jsp:getProperty, and EL’s BeanELResolver uses the same machinery for ${book.title}.

Table 2. The conventions that decide whether jsp:setProperty and jsp:getProperty can see a property
Convention Rule Consequence if broken

Public no-argument constructor

jsp:useBean with class instantiates with it

The value for the useBean class attribute [...] is invalid at translation time (observed)

Property from accessors

getX() and setX(T) define a property x; the type is the getter’s return and the setter’s parameter

Without the setter the property is read-only; without the getter it cannot be read with jsp:getProperty

Boolean property

isX() returning primitive boolean (and optionally getX())

A getter Boolean isX() returning the wrapper type is not a getter: Cannot find a method to read property [featured] (observed)

Property name decapitalization

Introspector.decapitalize: getTitle() gives title, but getISBN() gives ISBN and getURL() gives URL because the first two letters are capitals

property="isbn" fails with Cannot find any information on property [isbn]; the name must be ISBN (observed)

Indexed property

An array-valued getX()/setX(T[]), optionally with getX(int)/setX(int, T)

Introspector reports it as indexed (observed); jsp:setProperty needs an array value

Serializable

Implement java.io.Serializable for beans stored in a session

A distributable application fails to replicate the session otherwise

HttpSessionBindingListener

valueBound and valueUnbound are called when the bean is put into or removed from a session

Useful for releasing resources; see the example below

BeanInfo

A class named XBeanInfo next to X overrides introspection

Properties it does not list are invisible to jsp:setProperty and to EL (observed)

PropertyEditor

A registered editor converts a String to the property type with setAsText

Used by jsp:setProperty for types that are not primitives, wrappers or String

Records

A record’s accessors (title()) are not getters

jsp:getProperty fails: Cannot find any information on property [title]; EL 6.0 reads records (observed, see the example below)

The Bookshelf bean used by every example on this page is a plain class with the conventions above: a no-argument constructor, ordinary accessors, a boolean isAvailable(), an indexed tags property, Serializable, and a session binding listener that records its own lifecycle. The featured flag exists so that a later example can show why property="*" is dangerous. The Titled interface exists so that jsp:useBean can name a type that cannot be instantiated.

The Bookshelf book bean (src/main/java/org/example/bookshelf/BookBean.java)
package org.example.bookshelf;

import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class BookBean implements Titled, Serializable, HttpSessionBindingListener {
    private static final long serialVersionUID = 1L;
    public static final List<String> EVENTS = new ArrayList<>();   // demo only

    private long id;
    private String title = "";
    private String author = "";
    private int year;
    private boolean available;
    private String[] tags = new String[0];
    private boolean featured;

    public BookBean() {                       // required by jsp:useBean class="..."
    }

    public long getId() { return id; }
    public void setId(long id) { this.id = id; }

    @Override
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }

    public int getYear() { return year; }
    public void setYear(int year) { this.year = year; }

    public boolean isAvailable() { return available; }           // boolean property
    public void setAvailable(boolean available) { this.available = available; }

    public String[] getTags() { return tags; }                   // indexed property
    public void setTags(String[] tags) { this.tags = tags; }
    public String getTags(int index) { return tags[index]; }
    public void setTags(int index, String tag) { tags[index] = tag; }

    public String getNote() { return null; }                     // read-only, may be null

    public boolean isFeatured() { return featured; }             // must not come from a form
    public void setFeatured(boolean featured) { this.featured = featured; }

    @Override
    public void valueBound(HttpSessionBindingEvent event) { EVENTS.add("bound " + event.getName()); }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) { EVENTS.add("unbound " + event.getName()); }
}

// Titled.java
public interface Titled {
    String getTitle();
}

To see what the introspector sees, ask it. This page, and a second class Odd with a getISBN() accessor and a Boolean isFeatured() method, were run as a scriptlet purely for the demonstration (scriptlets are legacy; see the Scripting Elements (Legacy)).

demo/introspect.jsp, listing the properties Jasper and EL can use
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.beans.*" %>
<% for (PropertyDescriptor pd : Introspector.getBeanInfo(org.example.bookshelf.BookBean.class, Object.class).getPropertyDescriptors()) {
     out.println(pd.getName() + " " + pd.getPropertyType().getSimpleName() + (pd instanceof IndexedPropertyDescriptor ? " indexed" : "")); }
   out.println("--");
   for (PropertyDescriptor pd : Introspector.getBeanInfo(org.example.bookshelf.Odd.class, Object.class).getPropertyDescriptors()) {
     out.println(pd.getName() + " " + pd.getPropertyType().getSimpleName()); }
   out.println("--" + Introspector.decapitalize("URL") + " " + Introspector.decapitalize("Title") + " " + Introspector.decapitalize("XPos"));
%>
Output on Tomcat 11.0.26 (curl http://localhost:18080/g3actions/demo/introspect.jsp; note is omitted from the list for brevity)
author String
available boolean
featured boolean
id long
tags String[] indexed
title String
year int
--
ISBN String
featured Boolean
--URL title XPos

The second block shows both traps: the property is ISBN, not isbn, and featured appears only because of its setter (a Boolean-returning isFeatured() is not a getter). The third line is the decapitalization rule: Introspector.decapitalize("URL") returns URL unchanged because the first two characters are upper case.

jsp:useBean

jsp:useBean associates a Java object that lives in a scope with a name, creating the object first if it does not exist. It is the action that "introduces" a bean to the page: jsp:setProperty and jsp:getProperty are only valid for names introduced this way (or by a custom action that declares a variable of that name). In a scripting page it also declares a Java local variable of the same name; in a scriptless page or context only the EL variable exists.

Table 3. The attributes of jsp:useBean (Pages 4.0 table JSP.5-1)
Attribute Meaning Rules

id

The name of the object in its scope, and the scripting variable name

Required. Unique within the translation unit: a duplicate id is a fatal translation error (observed). Case sensitive and a legal Java variable name

scope

page (default), request, session or application

Anything else is a translation error: Illegal value of 'scope' attribute: [pagee] (observed)

class

Fully qualified class to instantiate when the object is not found

Must be non-abstract with a public no-argument constructor

type

The type of the scripting variable and the type the object must have

A superclass, an interface, or the class itself; a ClassCastException results otherwise (observed)

beanName

A name for java.beans.Beans.instantiate: a class name or a serialized resource a/b/c.ser

Accepts a request-time value. Used with type; class and beanName together are a translation error (observed)

At least one of type and class must be present, and class with beanName is not valid. When type and class are both present, class must be assignable to type; Jasper compiles the generated servlet with that assumption, so the mismatch becomes a Java compile error (Unable to compile class for JSP, observed).

What jsp:useBean Does, Step by Step

flowchart TD start["jsp:useBean id, scope, class, type, beanName"] --> find{"Object named id
in the scope?"} find -->|yes| cast["Cast to type, or to class
when type is absent"] cast -->|wrong type| cce["ClassCastException"] cast -->|ok| skip["Bean found: the body is ignored"] find -->|no| spec{"class or beanName
given?"} spec -->|neither| ie1["InstantiationException:
bean id not found within scope"] spec -->|class| cls{"Non-abstract class with a
public no-argument constructor?"} cls -->|no| ie2["InstantiationException, or a
translation error from Jasper"] cls -->|yes| make["new class()"] spec -->|beanName| bn["Beans.instantiate with the
web application class loader"] make --> store["Store in the scope under id
and declare the variable"] bn --> store store --> body["Run the body, if any:
the place for setProperty"]

The lookup, creation and storage are performed together; for session and application scope the inspection is done synchronized per scope namespace so that two requests cannot both create the bean. The synchronization ends with the action: calls to the bean’s own methods afterwards are not synchronized, so a bean in application scope is shared by every thread and has to be thread-safe (see Servlet Fundamentals for the threading model).

Creating and Using a Bean

demo/basic.jsp, create a request-scope bean, set two properties, read them three ways
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="request"/>
<jsp:setProperty name="book" property="title" value="Dune"/>
<jsp:setProperty name="book" property="year" value="1965"/>
<p>title=<jsp:getProperty name="book" property="title"/> year=<jsp:getProperty name="book" property="year"/></p>
<p>EL: ${book.title} ${book.year} ${book.available}</p>
<p>scriptlet: <%= book.getTitle().length() %></p>
<p>pageScope has: ${pageScope.book != null} requestScope: ${requestScope.book != null}</p>
Rendered HTML (the blank lines left by the actions are omitted)
<p>title=Dune year=1965</p>
<p>EL: Dune 1965 false</p>
<p>scriptlet: 4</p>
<p>pageScope has: false requestScope: true</p>

The same object is reached through the action, through EL and through the Java variable declared by jsp:useBean. The default scope is page; here request was chosen, which is why pageScope.book is empty and requestScope.book is not. Scope names and lookup order are covered in Implicit Objects & Scopes.

The Body Runs Only When the Bean Is Created

A non-empty body is processed only if the bean was just created, so it is the place to give a new bean its first values. On the second request in the same session the bean already exists and the body is skipped.

demo/body.jsp, a session-scope bean initialized once
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="session">
  <jsp:setProperty name="book" property="title" value="Created"/>
  <% out.println("[body ran]"); %>
</jsp:useBean>
title=<jsp:getProperty name="book" property="title"/>
<jsp:setProperty name="book" property="title" value="Changed"/>
Two requests with one cookie jar
$ curl -s -c jar.txt http://localhost:18080/g3actions/demo/body.jsp
  [body ran]
title=Created
$ curl -s -b jar.txt http://localhost:18080/g3actions/demo/body.jsp
title=Changed

The second response prints Changed: the jsp:setProperty outside the body ran on every request and the bean kept the value from the first one, which is exactly the session-scope behaviour and also why a session bean initialized in a body looks "stuck" to a page author who expected a reset.

Naming an Existing Object: type Without class

When a servlet or another page has already stored the object, a page gives it a local name with type alone. The object must exist; nothing is created. type may be an interface, a case that class cannot handle.

A servlet stores the bean, the view names it by interface
@WebServlet("/stash")
public class BooksServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BookBean b = new BookBean();
        b.setId(7);
        b.setTitle("Dune");
        b.setAuthor("Frank Herbert");
        b.setYear(1965);
        req.setAttribute("book", b);
        req.getRequestDispatcher("/WEB-INF/views/typeonly.jsp").forward(req, resp);
    }
}
WEB-INF/views/typeonly.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" type="org.example.bookshelf.Titled" scope="request"/>
title=<jsp:getProperty name="book" property="title"/> class=${book.getClass().simpleName}
curl output
$ curl -s http://localhost:18080/g3actions/stash
title=Dune class=BookBean

The two failure modes are the ones the specification lists. If no object exists the request fails with java.lang.InstantiationException: bean nothere not found within scope; if one exists but is of the wrong type the failure is java.lang.ClassCastException: class org.example.bookshelf.BookBean cannot be cast to class org.example.bookshelf.BookRecord (both observed). Both reach the client as JasperException wrapped in an HTTP 500 unless an error page handles them (see Error Handling).

beanName: Beans.instantiate

beanName hands the name to java.beans.Beans.instantiate with the web application’s class loader, so it can name a class or a serialized bean resource (a/b/c.ser). It is the one jsp:useBean attribute that accepts a request-time value, which is why a user-controlled beanName would be a deserialization and class-instantiation vector. Nothing in the Bookshelf needs it.

demo/beanname.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" beanName="org.example.bookshelf.BookBean" type="org.example.bookshelf.Titled" scope="page"/>
class=${book.getClass().name} title=[<jsp:getProperty name="book" property="title"/>]
curl output
class=org.example.bookshelf.BookBean title=[]

What Goes Wrong with jsp:useBean

Table 4. jsp:useBean errors and the messages Tomcat 11.0.26 produced (all observed)
Page Message Meaning

class names a type with no public no-argument constructor, or a class that does not exist

The value for the useBean class attribute [org.example.bookshelf.NoCtor] is invalid. (translation error)

Add the constructor, or fix the name

Two jsp:useBean with the same id

useBean: Duplicate bean name: [{0}] (translation error; the placeholder is not substituted by Tomcat 11.0.26)

Rename one

class and beanName together

jsp.error.usebean.notBoth (the resource key itself is shown, not its text)

Use only one

scope="pagee"

Illegal value of 'scope' attribute: [pagee] (must be one of "page", "request", "session", or "application")

Fix the value

type only, nothing in the scope

java.lang.InstantiationException: bean nothere not found within scope

Create the object first, or use class

type incompatible with the stored object

java.lang.ClassCastException

Name a supertype or interface of the real class

type and class not related

Unable to compile class for JSP (a Java compile error in the generated servlet)

Make class assignable to type

jsp:setProperty

jsp:setProperty sets one or more properties of a bean introduced earlier in the same file. The values may come from the request parameters, a string constant or a request-time expression. Property discovery is the ordinary JavaBeans introspection described above, including PropertyEditor and BeanInfo.

Table 5. The four forms of jsp:setProperty (Pages 4.0 section 5.2)
Form What it does Notes

<jsp:setProperty name="b" property="*"/>

Walks every request parameter and sets each property whose name matches a parameter, converting the string

A parameter whose value is "" is skipped. Parameters with no matching property are ignored. This is the mass-assignment form

<jsp:setProperty name="b" property="title"/>

Sets title from the request parameter of the same name

A missing or empty parameter makes the action a no-op

<jsp:setProperty name="b" property="title" param="name"/>

Sets title from the parameter name

param and value together are a translation error (observed)

<jsp:setProperty name="b" property="year" value="1965"/>

Sets year from a constant or a request-time value

The value is converted from String using the property’s type

The name attribute must be a name introduced by jsp:useBean, and the introducing action must come first in the page; jsp:setProperty placed before its jsp:useBean fails with java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "bean" is null at request time (observed). A jsp:setProperty for a property that does not exist fails with Cannot find any information on property [nosuch] in a bean of type [...] (observed).

Conversions from Strings

When the source is a request parameter or a constant string, the container converts it to the property type with the rules of table JSP.1-11. A conversion failure is an error, at translation time for a constant and at request time for a parameter.

Table 6. Conversions from String values (Pages 4.0 table JSP.1-11, section 1.14.2.1)
Property type Conversion

Bean property with a PropertyEditor

setAsText(String); with no editor, null for an empty string, otherwise an error

boolean, Boolean

Boolean.valueOf(String), so only true (any case) is true; an empty string gives false

byte, short, int, long, float, double and wrappers

The valueOf of the wrapper; 0 for an empty string

char, Character

charAt(0), or (char) 0 when empty

Object

A new String

The most common surprise is the HTML checkbox. A checkbox with no value attribute submits the string on, and Boolean.valueOf("on") is false; only the literal true turns a boolean property on. The next example makes that, and the empty-string rule, concrete.

demo/star.jsp, a bean with defaults filled by property="*"
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="request">
  <jsp:setProperty name="book" property="title" value="Default title"/>
  <jsp:setProperty name="book" property="year" value="2000"/>
</jsp:useBean>
<jsp:setProperty name="book" property="*"/>
id=${book.id} title=[${book.title}] author=${book.author} year=${book.year} available=${book.available} featured=${book.featured} tags=${book.tags[0]}|${book.tags[1]} len=${book.tags.length}
Observed requests and responses
$ curl -s "http://localhost:18080/g3actions/demo/star.jsp?id=5&title=Dune&author=Frank&year=1965&available=on&featured=true&tags=a&tags=b"
id=5 title=[Dune] author=Frank year=1965 available=false featured=true tags=a|b len=2

$ curl -s "http://localhost:18080/g3actions/demo/star.jsp?title=&year=&author=X"
id=0 title=[Default title] author=X year=2000 available=false featured=false tags=| len=0

$ curl -s "http://localhost:18080/g3actions/demo/star.jsp?available=true&id=1"
id=1 title=[Default title] author= year=2000 available=true featured=false tags=| len=0

Four facts are on display. The empty title= and year= parameters left the defaults alone, as the specification says. available=on produced false, available=true produced true. Repeated parameters (tags=a&tags=b) filled the String[] property. And featured=true, a parameter the form never offered, was accepted and set the featured property: that is mass assignment, explained below.

Why the Wildcard Form Is a Security Risk

property="*" lets the request decide which properties are set. Anyone who can send a request can send featured=true, id=1 or any other property name the bean happens to expose, whether or not the form shows a field for it. That is the same defect that OWASP calls mass assignment (and that Struts 7 counters by requiring the @StrutsParameter annotation on every settable property by default). A bean used with * therefore has to contain only properties that are safe to set from outside, or the page has to list its properties one at a time. The worked example later on this page uses the explicit form.

A conversion error is the second risk. A parameter such as year=abc is not validated: the exception propagates and the page fails with HTTP 500 (observed).

Observed failure for year=abc
$ curl -s -o /dev/null -w '%{http_code}\n' "http://localhost:18080/g3actions/demo/star.jsp?year=abc"
500
Message: An exception occurred processing [/demo/star.jsp] at line [6]
Root cause: java.lang.NumberFormatException: For input string: "abc"
            at org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(JspRuntimeLibrary.java:445)

The remedy for both risks is the same: bind and validate in a controller (a servlet or a framework), and hand the view an object that is already correct.

Request-Time Values, EL and Scriptlets

The value attribute accepts a request-time attribute value. With EL the conversion rules are those of section 1.23 of the EL specification, which is why value="${param.y + 1}" works for an int property: EL computes a Long and coerces it.

demo/param.jsp, param and an EL value
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="request"/>
<jsp:setProperty name="book" property="title" param="name"/>
<jsp:setProperty name="book" property="year" value="${param.y + 1}"/>
title=${book.title} year=${book.year}
curl output
$ curl -s "http://localhost:18080/g3actions/demo/param.jsp?name=Neuro&y=1983"
title=Neuro year=1984

A scripting expression in value gets no automatic conversion at all: its Java type must match the property. A String assigned to an int property fails at request time with java.lang.IllegalArgumentException: argument type mismatch, while <%= 1984 %> (an int) works (both observed). With EL the failure is an ELException (Cannot convert [abc] of type [class java.lang.String] to [int], observed).

demo/scriptval.jsp, the legacy form and its failure
<jsp:useBean id="book" class="org.example.bookshelf.BookBean"/>
<% String y = "1984"; %>
<jsp:setProperty name="book" property="year" value="<%= y %>"/>

The same attribute can be written as a jsp:attribute child, which is useful when the value is long or contains quotes. The body is trimmed by default, so the trailing newline below is not part of the value.

demo/attr.jsp, jsp:attribute supplying value and page
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="request"/>
<jsp:setProperty name="book" property="title">
  <jsp:attribute name="value">Dune, ${1 + 1}
  </jsp:attribute>
</jsp:setProperty>
[${book.title}]
<jsp:include>
  <jsp:attribute name="page">/WEB-INF/fragments/card.jsp</jsp:attribute>
  <jsp:body>
    <jsp:param name="heading" value="via attribute"/>
  </jsp:body>
</jsp:include>
curl output
[Dune, 2]
<div class="card"><h2>via attribute</h2> a=, size=</div>

Indexed Properties

Indexed properties are set from array values: property="*" or param with a repeated parameter fills a String[] (shown above). A single value string cannot set an array property: jsp:setProperty name="b" property="tags" value="a" fails with Cannot set indexed property (observed). Reading an element is an EL job: ${book.tags[0]}.

jsp:getProperty

jsp:getProperty writes the value of one property of a bean to the output, converted to a string "as in the println methods": primitives directly and objects with toString(). name is the bean introduced earlier and property is the property name. The specification says "if the object is not found, a request-time exception is raised", and it recommends a translation error when the name was never introduced by jsp:useBean or a custom action.

Tomcat 11.0.26 takes the recommendation: a bean that a servlet stored with setAttribute is not visible to jsp:getProperty unless the page introduces it with jsp:useBean (type only is enough); otherwise the page does not compile (observed).

Translation error for an attribute set by a servlet and never introduced
$ curl -s "http://localhost:18080/g3actions/stash?view=stash"
org.apache.jasper.JasperException: File [/WEB-INF/views/stash.jsp(3,0)] uses name [book] in jsp:getProperty for a bean that was not previously introduced as per JSP.5.3
Table 7. Differences between jsp:getProperty and EL for the same bean
Situation jsp:getProperty ${book.note} (EL)

Property value is null

Prints the text null (observed)

Prints nothing (observed)

HTML special characters in the value

Written unescaped (observed, below)

Also unescaped in template text; use c:out or fn:escapeXml

Bean not introduced by jsp:useBean

Translation error (observed)

Found through pageContext.findAttribute in any scope

Property does not exist

Cannot find any information on property [isbn] in a bean of type [...] (observed)

PropertyNotFoundException

Record component

Not a bean property: error (observed)

Works in EL 6.0 (observed)

Nested or indexed access

Not possible

${book.tags[0]}, ${shelf.books[1].title}

demo/getnull.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean"/>
[<jsp:getProperty name="book" property="note"/>] [${book.note}]
curl output
[null] []

The unescaped output is a cross-site scripting vector, which is the single best reason to stop using the tag:

demo/xss.jsp, jsp:getProperty against c:out
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean"/>
<jsp:setProperty name="book" property="title" param="t"/>
raw: <jsp:getProperty name="book" property="title"/>
escaped: <c:out value="${book.title}"/>
curl output
$ curl -s -G http://localhost:18080/g3actions/demo/xss.jsp --data-urlencode 't=<script>alert(1)</script>'
raw: <script>alert(1)</script>
escaped: &lt;script&gt;alert(1)&lt;/script&gt;

A record, which EL 6.0 reads without getters, is invisible to the action. With BookRecord(long id, String title) stored by the servlet as rec, ${rec.title} prints Neuromancer, while jsp:useBean id="rec" type="...BookRecord" followed by jsp:getProperty name="rec" property="title" fails with Cannot find any information on property [title] in a bean of type [org.example.bookshelf.BookRecord] (both observed).

Bean Tags or EL? and Mixing with Scriptlets

The standard-action approach and the EL approach read the same objects; the difference is how much the page is responsible for. The table compares the styles on the Bookshelf’s book.

Table 8. The same task in the three styles
Task Standard actions (legacy) Today

Get a bean into the page

jsp:useBean with class or type

The controller stores it with setAttribute; the page uses EL (${book})

Read a property

jsp:getProperty (unescaped)

<c:out value="${book.title}"/>

Set properties from a form

jsp:setProperty property="*"

The controller binds, validates and converts; the page only displays

Share a bean across pages

jsp:useBean with scope

request, session or application attributes set by the controller

Initialize a new bean

The jsp:useBean body

A constructor, a factory or the controller

Use the bean in Java code

The scripting variable named by id

No Java in the page; call a method in the bean or the controller

Because jsp:useBean declares a Java variable named by id, scriptlets and bean tags can be mixed, as in basic.jsp above where <%= book.getTitle().length() %> printed 4. The specification warns that the object in the pageContext is "the truth" and that a scripting variable is only a cache of the reference. Mixed pages are among the hardest to maintain, which is why scripting-invalid exists. The tag files, EL and JSTL pages show the replacements.

A bean that implements HttpSessionBindingListener is told when it enters and leaves a session. The next example puts a bean into the session with jsp:useBean and invalidates the session in a second request, then prints the events.

demo/binding.jsp and demo/binding-clear.jsp
<%-- binding.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="session"/>
<%= org.example.bookshelf.BookBean.EVENTS %>

<%-- binding-clear.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<% session.invalidate(); %>
<%= org.example.bookshelf.BookBean.EVENTS %>
curl output with a cookie jar
$ curl -s -c jar.txt http://localhost:18080/g3actions/demo/binding.jsp
[bound book]
$ curl -s -b jar.txt http://localhost:18080/g3actions/demo/binding-clear.jsp
[bound book, unbound book]

valueUnbound also runs when the session expires or the attribute is replaced, so it is a reliable place to close resources a session bean holds. Storing a bean in the session makes it part of the session’s state, and the session rules (serializability for distributable applications, size, fixation) apply to it; see Servlet Fundamentals.

A BeanInfo changes what is a property. With a HiddenBeanBeanInfo that lists only title, a page that sets property="*" with secret=HACK leaves secret alone, and ${h.secret} fails with PropertyNotFoundException: Property [secret] not found on type [org.example.bookshelf.HiddenBean] (observed). That is a legitimate way to protect a bean that has to be used with *, but it is invisible in the class itself, so the explicit form of jsp:setProperty is easier to review.

jsp:include

jsp:include includes the output of another resource into the current output at request time. The resource may be a JSP, a servlet, or a static file; it is addressed with a relative URL specification that is interpreted in the context of the web application, and that is mapped like any other request. The included resource shares the request and its attributes (so a request-scope bean is visible to it) but cannot change the response status or set headers, a constraint equivalent to RequestDispatcher.include. Request processing resumes in the calling page afterwards.

Table 9. Attributes of jsp:include (Pages 4.0 table JSP.5-4)
Attribute Meaning Rules

page

The relative URL specification of the resource

Required; accepts a request-time value that evaluates to a String. Relative paths are interpreted relative to the current page

flush

Whether to flush the buffer before the include

Optional boolean; the default is false

demo/include.jsp and the fragment it includes (request parameters are written through fn:escapeXml)
<%-- demo/include.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %><%@ taglib prefix="fn" uri="jakarta.tags.functions" %>
before
<jsp:include page="/WEB-INF/fragments/card.jsp" flush="false">
  <jsp:param name="heading" value="Bookshelf"/>
  <jsp:param name="a" value="bar"/>
</jsp:include>
after: heading=[${fn:escapeXml(param.heading)}] a=${fn:escapeXml(paramValues.a[0])}

<%-- WEB-INF/fragments/card.jsp --%>
<%@ taglib prefix="fn" uri="jakarta.tags.functions" %><div class="card"><h2>${fn:escapeXml(param.heading)}</h2> a=${fn:escapeXml(paramValues.a[0])},${fn:escapeXml(paramValues.a[1])} size=${paramValues.a.length}</div>
Observed output
$ curl -s "http://localhost:18080/g3actions/demo/include.jsp"
before
<div class="card"><h2>Bookshelf</h2> a=bar, size=1</div>
after: heading=[] a=

$ curl -s "http://localhost:18080/g3actions/demo/include.jsp?a=foo&heading=Orig"
before
<div class="card"><h2>Bookshelf</h2> a=bar,foo size=2</div>
after: heading=[Orig] a=foo

Two facts follow. The new parameters are in effect only during the include: after the action the including page sees the original request, whose heading is Orig and whose a is foo (heading=[] and an empty a when the original had none). And jsp:param values are prepended to existing ones, so a request parameter a=foo plus <jsp:param name="a" value="bar"/> gives a=bar,foo in the included page, where ${param.a} is the first value bar and the original is still reachable at paramValues.a[1]. The new value takes precedence; it does not replace the old one.

The relative-path rule is easy to get wrong when two directories are involved. Pages 4.0 gives four combinations of the include directive and the action:

Table 10. Resolving the relative path C.jsp from dir/B.jsp (spec section 5.4)
A.jsp says dir/B.jsp says C.jsp resolves to

<%@ include file="dir/B.jsp" %>

<%@ include file="C.jsp" %>

dir/C.jsp

<jsp:include page="dir/B.jsp"/>

<jsp:include page="C.jsp"/>

dir/C.jsp

<jsp:include page="dir/B.jsp"/>

<%@ include file="C.jsp" %>

dir/C.jsp

<%@ include file="dir/B.jsp" %>

<jsp:include page="C.jsp"/>

C.jsp (the directive merges B.jsp into A.jsp at translation time, so "the current page" is A.jsp)

The static-include directive, the three include mechanisms side by side, prelude and coda, and c:import are compared in Includes, Forwards & Page Composition; this page covers only the action and its parameters.

Two Traps: A Fragment That Is Not a JSP, and a Page Chosen by the Request

A fragment saved as card.jspf is not translated by Tomcat unless the web application declares that extension in a jsp-property-group: Jasper’s default mapping covers *.jsp and *.jspx only. Including /WEB-INF/fragments/card.jspf served the file as text and printed ${param.heading} literally (observed). Name included fragments .jsp (or map the extension, see Configuration and Property Groups).

The second trap is a security problem. page accepts a request-time value, so <jsp:include page="${param.p}"/> lets the client choose what is included. On Tomcat 11.0.26 the following page returned the application’s /WEB-INF/web.xml:

demo/e11.jsp, an include controlled by the request (never write this)
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:include page="${param.p}"/>
Observed: the container-internal descriptor is returned to the client
$ curl -s "http://localhost:18080/g3actions/demo/e11.jsp?p=/WEB-INF/web.xml"
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" ... version="6.1">
</web-app>

WEB-INF is protected against client requests, not against a dispatch from inside the application. The rule is the same as for any path traversal: choose included resources from a fixed allow-list (a map from a safe key to a path), never from the request.

jsp:forward

jsp:forward dispatches the current request to another resource in the same application and ends the current page: nothing after the action is executed. It is RequestDispatcher.forward from inside a page, with the same effects on the request path attributes. If the page output is buffered, the buffer is cleared before the forward. If the buffer was already flushed, or if an unbuffered page had written anything, the forward cannot work: the specification demands an IllegalStateException.

jsp:forward accepts the same jsp:param children as jsp:include, with the same ordering rules, and a single attribute page, which accepts a request-time value (and has the same path-selection risk as in jsp:include).

demo/forward.jsp, with the page’s own output discarded
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
this text is discarded
<jsp:forward page="/WEB-INF/fragments/card.jsp">
  <jsp:param name="heading" value="Forwarded"/>
  <jsp:param name="a" value="bar"/>
</jsp:forward>
never reached
Observed output
$ curl -s "http://localhost:18080/g3actions/demo/forward.jsp?a=foo&heading=Orig"
<div class="card"><h2>Forwarded</h2> a=bar,foo size=2</div>

The text before the forward and the text after it do not appear. The next page flushes the buffer first.

demo/forward-late.jsp, forward after flush
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<% out.flush(); %>
<jsp:forward page="/WEB-INF/fragments/card.jsp"/>

Tomcat 11.0.26 did not throw the IllegalStateException of the specification: the JSP writer’s clear() raised java.io.IOException: Error: Attempt to clear a buffer that's already been flushed, the error-page mechanism could not run because the response was committed (too late for an error page in the log), and the client received an HTTP 200 with an empty body (observed). The practical rule is the one from the servlet fundamentals page: decide to forward before writing anything, which also means before any <% out.flush() %> and before a full buffer.

A forward is invisible to the browser (the URL does not change) and is only appropriate when the destination is part of the same logical request; after a state change use a redirect (see Includes, Forwards & Page Composition).

jsp:param Rules in Brief

jsp:param has two mandatory attributes, name and value, and the value may be a request-time expression. It is valid only inside jsp:include, jsp:forward (and the removed jsp:params); anywhere else the page is rejected: The jsp:param action must not be used outside the jsp:include or jsp:forward elements (observed). Names and values are written unencoded by the page author; the container encodes them with the request’s character encoding when it has to build a query string, which is why a value containing an ampersand needs no manual escaping. New parameters take precedence over existing ones of the same name and are visible only during the included or forwarded call.

Generating Markup: jsp:element, jsp:attribute, jsp:body, jsp:text and jsp:output

jsp:element creates an element whose name is only known at request time, jsp:attribute gives it attributes (and, as shown above, can also give attributes to other actions), jsp:body gives it content, and jsp:text writes template text verbatim.

Table 11. Attributes of jsp:attribute (Pages 4.0 table JSP.5-6)
Attribute Meaning

name

Required. With jsp:element, the attribute name in the generated element; otherwise it must match an attribute of the enclosing action, as declared in the TLD or as specified. May be a QName. A translation error results if the value is also given as an XML attribute

trim

true (default) removes leading and trailing whitespace of the body, at translation time only; whitespace produced by an action in the body is never trimmed

omit

With jsp:element only: when true, the attribute is left out of the generated element. Ignored elsewhere. Default false

jsp:body takes no attributes and is required whenever jsp:attribute children are present. If jsp:attribute children are present and there is no (or an empty) jsp:body, the action has an empty body.

jsp:element has one mandatory attribute, name, which must be an XML attribute (not a jsp:attribute) because jsp:attribute has a special meaning inside jsp:element. The name may be a request-time value.

demo/element.jsp, a computed element with attributes
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%
  pageContext.setAttribute("tagName", "li");
  pageContext.setAttribute("lang", "en");
%>
<jsp:element name="${tagName}">
  <jsp:attribute name="lang">${lang}</jsp:attribute>
  <jsp:attribute name="hidden" trim="true" omit="true">x</jsp:attribute>
  <jsp:attribute name="class">
      book
  </jsp:attribute>
  <jsp:body>Dune</jsp:body>
</jsp:element>
<jsp:element name="hr"/>
Observed output
<li lang="en" class="book">Dune</li>
<hr/>

The class value was trimmed, the hidden attribute was omitted, and an element with no body was written as an empty tag. The element name must never come from the request: Jasper accepts any name as is ("JSP 4.0 places no constraints on this value"), so a computed name from user input is markup injection.

jsp:text

jsp:text encloses template data and has no attributes. Its body is passed through to the output unchanged, without the whitespace removal that applies elsewhere, and it may contain EL, but no nested actions or scripting elements. Inside a JSP document the body must also be well-formed XML, so a literal < or & has to be escaped or put into a CDATA section.

demo/text-ok.jsp and demo/text2.jsp
<%-- text-ok.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
[<jsp:text>  spaces  </jsp:text>] [<jsp:text>a &lt; b ${1 + 1}</jsp:text>]

<%-- text2.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
[<jsp:text><![CDATA[<b>not bold</b>]]> ${1 + 1}</jsp:text>]
Observed output
[  spaces  ] [a &lt; b 2]
[<b>not bold</b> 2]

Nesting jsp:text inside jsp:text is rejected, with Tomcat 11.0.26 reporting '<', when appears in the body of <jsp:text>, must be encapsulated within a CDATA (observed).

jsp:output

jsp:output can be used only in JSP documents and in tag files in XML syntax, where it controls the XML declaration and the DOCTYPE of the generated output. Pages 4.0 defines four properties, all affecting the prolog: omit-xml-declaration (yes, no, true, false; the default is yes for a document that has a jsp:root element), doctype-root-element, doctype-system and doctype-public. A DOCTYPE is generated only if doctype-system is present, and doctype-root-element must then be present; doctype-public needs doctype-system. In a standard-syntax page the action is a translation error: <jsp:output> must not be used in standard syntax (observed).

demo/doc.jspx, a JSP document with jsp:output and the bean tags
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="jakarta.tags.core">
  <jsp:output doctype-root-element="html" doctype-system="about:legacy-compat" omit-xml-declaration="true"/>
  <jsp:directive.page contentType="text/html; charset=UTF-8"/>
  <body>
    <h1><jsp:text>Bookshelf</jsp:text></h1>
    <jsp:useBean id="book" class="org.example.bookshelf.BookBean"/>
    <jsp:setProperty name="book" property="title" value="Dune"/>
    <p>${book.title} <jsp:getProperty name="book" property="title"/></p>
  </body>
</html>
Observed output
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html><body><h1>Bookshelf</h1><p>Dune Dune</p></body></html>

The identifier http://java.sun.com/JSP/Page is the namespace name of the standard actions in XML syntax. It is an identifier, not an address, and Pages 4.0 still uses it in its own examples. JSP documents are covered in JSP Documents (XML Syntax).

jsp:invoke and jsp:doBody (Tag Files)

jsp:invoke and jsp:doBody are valid only inside tag files, and a translation error results if they are used in a page. jsp:invoke fragment="name" invokes a fragment attribute (declared with fragment="true"); jsp:doBody evaluates the body passed to the tag. Both write to the current output by default; var (a String) or varReader (a java.io.Reader, resettable) captures the output in a scoped variable, and scope (default page) says which scope, valid only with var or varReader. A session scope throws IllegalStateException when the calling page has no session. The action has no body. This example is only a pointer; the full treatment is on the tag file pages of this section.

WEB-INF/tags/panel.tag
<%@ tag body-content="scriptless" %>
<%@ attribute name="header" fragment="true" %>
<%@ attribute name="title" required="true" %>
<section><h2>${title}</h2>
<header><jsp:invoke fragment="header"/></header>
<jsp:doBody var="inner" scope="page"/>
<div>[${inner}]</div>
<jsp:doBody/>
</section>
demo/tagfile.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
<t:panel title="Books">
  <jsp:attribute name="header">Header: ${1 + 2}</jsp:attribute>
  <jsp:body>Dune</jsp:body>
</t:panel>
Observed output
<section><h2>Books</h2>
<header>Header: 3</header>
<div>[Dune]</div>
Dune
</section>

Here jsp:attribute supplies the fragment attribute header, jsp:body supplies the body, jsp:invoke writes the fragment, and jsp:doBody is used twice: once captured into inner and once written directly.

jsp:plugin Is Gone

jsp:plugin (with jsp:params and jsp:fallback) generated the object or embed markup that loaded an applet or a bean into the browser’s Java plug-in. Applets no longer run in browsers, and the action was deprecated in Pages 3.1 and removed in 4.0 ("Remove the deprecated jsp:plugin action and related actions"). A page that uses it no longer compiles (observed):

demo/plugin.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:plugin type="applet" code="Foo.class" width="1" height="1"><jsp:fallback>no</jsp:fallback></jsp:plugin>
curl output on Tomcat 11.0.26
org.apache.jasper.JasperException: /demo/plugin.jsp (line: [2], column: [5]) Invalid standard action

There is no replacement for an applet. A page that rendered one needs a rewrite to ordinary HTML and JavaScript, or a server-side equivalent.

The Bookshelf Worked Example: A Book Card from a Form

The Bookshelf needs a "preview" page that shows a book typed into a form. The legacy shape binds with property="*" and prints with jsp:getProperty. The shape below keeps the standard actions but removes the two dangers: it lists the three properties the form may set, and it prints through c:out. A fragment renders the card; it receives its bean through the request scope and a heading level through jsp:param.

demo/worked.jsp, bean creation, explicit properties and an include
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<jsp:useBean id="book" class="org.example.bookshelf.BookBean" scope="request"/>
<%-- only the three properties the form is allowed to set, never property="*" --%>
<jsp:setProperty name="book" property="title"/>
<jsp:setProperty name="book" property="author"/>
<jsp:setProperty name="book" property="year"/>
<!DOCTYPE html>
<html lang="en">
<body>
<jsp:include page="/WEB-INF/fragments/book-card.jsp">
  <jsp:param name="level" value="2"/>
</jsp:include>
</body>
</html>
WEB-INF/fragments/book-card.jsp, a fragment that names the bean by type and renders it
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<jsp:useBean id="book" type="org.example.bookshelf.BookBean" scope="request"/>
<article>
  <jsp:element name="h${param.level}">
    <jsp:attribute name="class">title</jsp:attribute>
    <jsp:body><c:out value="${book.title}"/></jsp:body>
  </jsp:element>
  <p>by <c:out value="${book.author}"/> (${book.year})</p>
</article>
Observed: an attempt to set featured and a markup-injection title
$ curl -s "http://localhost:18080/g3actions/demo/worked.jsp?title=Dune&author=Frank%20Herbert&year=1965&featured=true&level=1"
<!DOCTYPE html>
<html lang="en">
<body>
<article>
  <h2 class="title">Dune</h2><p>by Frank Herbert (1965)</p>
</article>

</body>
</html>

$ curl -s -G "http://localhost:18080/g3actions/demo/worked.jsp" --data-urlencode 'title=<script>x</script>' -d author=A -d year=1
...
  <h2 class="title">&lt;script&gt;x&lt;/script&gt;</h2><p>by A (1)</p>
...

What the run shows: the level=1 the client sent was overridden by the page’s own jsp:param (the heading is an h2), featured=true was ignored because the page never names that property, the title was escaped by c:out, and the fragment found the bean through jsp:useBean type because the request scope is shared by an included page. The year is printed with plain EL: a number needs no escaping. A form that must reject year=abc still needs a controller, because jsp:setProperty throws.

In the real Bookshelf both files live under WEB-INF/views and WEB-INF/fragments, reached by a servlet that forwards to them; the examples on this page were served from a top-level demo folder only so that curl could reach every one directly. The servlet fundamentals page shows the controller side (Servlet Fundamentals).

Pitfalls and Error Names

Table 12. Standard-action pitfalls, their symptoms and fixes (rows marked "observed" were reproduced on Tomcat 11.0.26)
Symptom or error Cause Fix

Cannot find any information on property [isbn] in a bean of type [...] (observed)

The accessor is getISBN(), so the property is ISBN; or the property does not exist

Use the introspector’s name; run the introspection page above to see the real names

Cannot find a method to read property [featured] (observed)

The getter returns Boolean (a wrapper) or has no getter

Return primitive boolean from isX(), or use getX()

Checkbox always false (observed)

The browser sends on, and Boolean.valueOf("on") is false

Give the checkbox value="true", or bind in a controller

HTTP 500 NumberFormatException from property="*" (observed)

A non-numeric parameter for a numeric property; no validation

Validate and bind in a controller; at least use jsp:setProperty on a bean whose setters accept strings

A property is set that the form never offered (observed)

property="*" is mass assignment

List properties one by one, keep sensitive properties out of the bean, or use BeanInfo

uses name [book] in jsp:getProperty for a bean that was not previously introduced as per JSP.5.3 (observed)

A servlet set the attribute but the page has no jsp:useBean

Add jsp:useBean id="book" type="..." scope="request", or use EL

java.lang.NullPointerException: ... "bean" is null at jsp:setProperty (observed)

jsp:setProperty appears before the jsp:useBean that creates the bean

Put the jsp:useBean first

InstantiationException: bean x not found within scope (observed)

jsp:useBean with type only and nothing in the scope

Make the controller store the bean first, or add class

ClassCastException at jsp:useBean (observed)

The stored object is not of type

Name an interface or superclass the object implements

The jsp:useBean body does not run again (observed)

It runs only when the bean is created

Put per-request updates outside the body

IllegalArgumentException: argument type mismatch (observed)

A scripting expression of the wrong Java type in value

Use an EL expression or a string

Cannot set indexed property (observed)

A single value string for an array property

Use property="*" or param with repeated parameters, or set the array in Java

Unescaped HTML from jsp:getProperty (observed)

The action does not escape

c:out or fn:escapeXml

jsp:getProperty fails on a record (observed)

Record accessors are not getters

Use EL, or a class with getters

jsp:include returns WEB-INF/web.xml (observed)

page="${param.p}": the request chooses the resource

Allow-list the target; never take a path from the request

An included file shows ${...} literally (observed)

The extension (.jspf) is not mapped to the JSP servlet

Name it .jsp, or declare the extension in a jsp-property-group

A forwarded page returns 200 with an empty body (observed)

The buffer was flushed before jsp:forward; Tomcat raised an IOException after the commit

Forward before writing; avoid out.flush() and large output before it

Included page cannot set a cookie, status or header

The include constraint (like RequestDispatcher.include); the attempt is ignored

Do it in the including page or in a controller

A jsp:param appears to have no effect after the include (observed)

Parameters exist only for the duration of the call

Pass the data in a request attribute or a bean

jsp:param outside jsp:include/jsp:forward (observed)

Valid only inside those two actions

Move it, or set an attribute

jsp:param and a request parameter of the same name (observed)

New values precede old ones: paramValues has both

Read ${param.name} (the new one) or index paramValues

jsp:text nested in jsp:text or a bare < in its body (observed)

The body takes only text and EL

Use an entity or CDATA

<jsp:output> must not be used in standard syntax (observed)

It is valid only in JSP documents and XML-syntax tag files

Use contentType and a literal DOCTYPE

Invalid standard action for jsp:plugin (observed)

The action was removed in Pages 4.0

Rewrite the page without an applet

jsp.error.setProperty.invalid or jsp.error.usebean.notBoth shown instead of a message (observed)

Tomcat 11.0.26 prints the resource key for these errors; useBean: Duplicate bean name: [{0}] shows an unsubstituted placeholder

The cause is: param with value; class with beanName; a repeated id

jsp:invoke or jsp:doBody in a JSP page

Valid only in tag files

Move the code into a tag file

What Changed Recently

Table 13. Standard actions across Pages versions
Version Change

Pages 4.0 (Jakarta EE 11, Tomcat 11.0.x)

jsp:plugin, jsp:params, jsp:fallback removed; isThreadSafe page attribute removed; deprecated jakarta.servlet.jsp.el classes removed; minimum Java 17; ErrorData gains query_string and method

Pages 3.1 (Jakarta EE 10)

jsp:plugin and related actions deprecated (no browser supports the markup); isThreadSafe deprecated; optional PropertyNotFoundException for unknown identifiers in EL; clarifications about scripting variable scope

Pages 3.0 (Jakarta EE 9)

Package rename javax.servlet.jsp to jakarta.servlet.jsp; deprecated methods annotated and generics added, the actions unchanged

JSP 2.1 and later

Deferred expressions #{} and a unified EL; ${} is allowed in template text and attribute values

JSP 2.0

jsp:attribute, jsp:body, jsp:element, jsp:invoke, jsp:doBody and tag files; EL in attributes and template text

Two behaviours of Tomcat 11.0.26 are worth knowing because they differ from older material: the jakarta.tags.core URI is the Jakarta Tags 3.0 identifier for the core library, although the old http://java.sun.com/jsp/jstl/core identifier was still accepted by the JSTL 3.0.1 implementation in a test page (observed), and a record, supported by EL 6.0, is not supported by the bean actions.

Where the Books Are Outdated

Web Development with JavaServer Pages (Manning, listed in the section bibliography) devotes two chapters to "using JSP components" and "developing JSP components" and builds its examples around the bean actions, and JSTL in Action and the Struts books describe the same idea in their own tag libraries. The concepts survive; the details have not.

Table 14. Component-chapter material and the present
The book’s era Today (Pages 4.0)

jsp:useBean, jsp:setProperty, jsp:getProperty as the way to build pages

Controller plus EL and JSTL; the actions remain valid but are legacy

property="*" as the standard form-binding technique

Mass assignment; bind and validate in a controller, or in Struts with @StrutsParameter

jsp:getProperty for output

c:out, or EL with fn:escapeXml; the action never escapes

Beans as the "component model" of JSP, with BeanInfo and PropertyEditor to customize binding

Still supported by java.beans; used by tools and frameworks, rarely hand-written

javax.servlet.jsp classes and <%@ taglib uri="http://java.sun.com/jsp/jstl/core" %>

jakarta.servlet.jsp and jakarta.tags.core

jsp:plugin and applets for rich clients

Removed in Pages 4.0

Scripting variables and scriptlets mixed with bean tags

Scriptless pages (scripting-invalid), tag files and EL

Hand-built examples for a banner rotator, quote generator, mail form, "who is" client, directory listing and a "view source" button

The ideas survive; the directory listing and view-source buttons are information-disclosure risks, and a form that mails or fetches a URL needs input validation and rate limits

jsp:include written with flush="true" everywhere

flush is optional and defaults to false; leave it out unless the included page must see a flushed buffer

jsp:forward or jsp:include with a path built from request data

A path-traversal and WEB-INF disclosure risk (verified above)

Typos in the book’s listings such as jsp:setPropety or jsp:sriptlet

Fail with Invalid standard action or a similar translation error; copy from the official specification instead

References