Jan 3, 2012

JSTL Core Set Tag Gotcha

Migrating a JSP-heavy project from JSTL 1.0 to 1.2, some odd behavior broke at least one bit of JSP logic. After much debugging, it was determined that the problem lie with the JSTL Core taglib, specifically in the Set tag.

In our case, the tag was being used to set an attribute to the value of another attribute’s inner property, as the following shows:

<c:set var="something"
    value="${somethingElse.property}"/>

Even more, the same attribute ame was being used in different JSPs, connected by including one in another, although storing the attribute in different scopes, as the following shows:

<c:set var="something"
    value="${somethingElse.property}" scope="request" />
<c:set var="something"
    value="${anotherElse.property}" />

In this case, the including page contained the first line, the request-scope set value. The included file contained the second, implicit scope value. The page was included with the request-time <c:import url=”included.jsp”/>, which “calls” the included page. The included page gets its own page context, but shares the request and session. When things worked well, the request value was unmodified, and “returning” from the included page left the page attribute behind. The discovered problem occurred when the value of the property resolved to NULL. In this case, not only was the page attribute “unset,” but it happened that the request attribute was also lost.

It was unsettling to find that what seemed to be a default page context action would affect the other scopes, but it turns out to be the case!

The following JSP shows this with a simple example. A few values are set, just to show what’s happening, and then the attributes are modified to show the results.

<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="something"
    value="Saved in Session" scope="session" />
<c:set var="something"
     value="Saved in Request" scope="request" />
<c:set var="something"
     value="Saved in Page" scope="page" />
<html>
<head>
<style type="text/css">
.value {
color: green;
}
</style>
<title>Test</title>
</head>
<body>
<p>
Session:
<span class="value"><%=session.getAttribute("something")%></span>
<br />Request:
<span class="value"><%=request.getAttribute("something")%></span>
<br />Page:
<span class="value"><%=pageContext.getAttribute("something")%></span>
<br />EL: <span class="value">${something}</span>
</p>
<c:set var="something" value="${nothing}" scope="page" />
<p>
Session:
<span class="value"><%=session.getAttribute("something")%></span>
<br />Request:
<span class="value"><%=request.getAttribute("something")%></span>
<br />Page:
<span class="value"><%=pageContext.getAttribute("something")%></span>
<br />EL: <span class="value">${something}</span>
</p>
<c:set var="something" value="${nothing}" />
<p>
Session:
<span class="value"><%=session.getAttribute("something")%></span>
<br />Request:
<span class="value"><%=request.getAttribute("something")%></span>
<br />Page:
<span class="value"><%=pageContext.getAttribute("something")%></span>
<br />EL: <span class="value">${something}</span>
</p>
</body>
</html>

The actions are pretty simple.

  • The first block shows all of the values as initially set. Since the page attribute is set, the EL resolves to the page-scoped value.
  • The page-scope value is set to something that resolves to NULL (note this doesn’t set the value to NULL, but removes the value from the attributes Map, which will resolve to NULL in future uses) with an explicit scope definition. This correctly clears the page attribute, but leaves the other alone, as is demonstrated by the EL evaluating to the request-scoped value.
  • The final block sets the attribute with an implicit scope definition; the documentation says this will default to the page scope (which it does), but what the documentation fails to note is that the other scopes are also removed! This is demonstrated by all of the “null” entries, and the empty EL (EL doesn’t show NULL values).

Here’s what the output looks like:

Session: Saved in Session
Request: Saved in Request
Page: Saved in Page
EL: Saved in Page

Session: Saved in Session
Request: Saved in Request
Page: null
EL: Saved in Request

Session: null
Request: null
Page: null
EL:

This “undocumented feature” is found in the implementation of the org.apache.taglibs.standard.tag.common.core.SetSupport.doEndTag() which has the following bit of close in it:

if (scopeSpecified)
    pageContext.removeAttribute(var, scope);
else
    pageContext.removeAttribute(var);

It would seem that it should be the case that if the scope isn’t specified that the page scope would be used, as that is the default according to the documentation. It isn’t the case, though, so when not specified the attribute looks to be entirely removed. Checking deeper, at least in the Tomcat source, the pageContext.removeAttribute(var) ends up in the JSTL org.apache.jasper.runtime.PageContextImpl which finally results in executing the following:

private void doRemoveAttribute(String name) {
    removeAttribute(name, PAGE_SCOPE);
    removeAttribute(name, REQUEST_SCOPE);
    if( session != null ) {
        try {
            removeAttribute(name, SESSION_SCOPE);
        } catch(IllegalStateException ise) {
            // Session has been invalidated.
            // Ignore and fall throw to application scope.
        }
    }
    removeAttribute(name, APPLICATION_SCOPE);
}

As we can see, it removes the attributes from all of the scopes, even the application (the Servlet’s Context) scope, which we didn’t test! As disconcerting as this is, the solution is trivial to implement: explicitly name the page scope where the attribute may collide and where the value may evaluate to NULL. As long as both of those don’t happen, the problem doesn’t occur. If they do both happen, though, no matter where the <c:set> is encountered, the request and session attributes are lost.

This has been tested on Tomcat v6, v7, and Weblogic v11.

About the Author

Object Partners profile.

One thought on “JSTL Core Set Tag Gotcha

  1. sajid says:

    how to invalidate session using c:foreach

    1. jack says:

      invalidate session?

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Android Development for iOS Developers
Android development has greatly improved since the early days. Maybe you tried it out when Android development was done in Eclipse, emulators were slow and buggy, and Java was the required language. Things have changed […]
Add a custom object to your Liquibase diff
Adding a custom object to your liquibase diff is a pretty simple two step process. Create an implementation of DatabaseObject Create an implementation of SnapshotGenerator In my case I wanted to add tracking of Stored […]
Keeping Secrets Out of Terraform State
There are many instances where you will want to create resources via Terraform with secrets that you just don’t want anyone to see. These could be IAM credentials, certificates, RDS DB credentials, etc. One problem […]
Validating Terraform Plans using Open Policy Agent
When developing infrastructure as code using terraform, it can be difficult to test and validate changes without executing the code against a real environment. The feedback loop between writing a line of code and understanding […]