A teammate and I were foiled by an interesting JSTL quirk this week. The code looked something like this
<c:if test="${not empty aquarium.fish}">
<!---- feed the fish or something ---->
</c:if>
THE PROBLEM
The problem was, the fish were being fed whether the aquarium was empty or not.
The code
<c:if test="${!empty aquarium.fish}">
behaved the same, weird way. But we were sure, either way, that our syntax was correct. So that leaves only one question: WTF?
We know that we’re receiving an instance of aquarium from a method call developed by another team. We know that getFish() returns a java.util.Collection. Other than that we don’t care about the implementation, we just want our fish. In theory.
So we dug into the code for the other team’s getFish() method, and found out that our Collection is actually implemented as a HashSet. And JSTL behaves a little weird with respect to the empty operator: it does not work with java.util.Set. Incidentally this isn’t a problem in JSTL 1.1 or 1.2, and unfortunately we’re stuck using JSTL 1.0 for now. So unfortunately this forces us to know how this Collection is implemented in order to know how to handle it.
WHY?
As of JSTL 1.1, “… the Expression Language now belongs to the JSP specification (JSP 2.0).” [from the JSTL 1.1 spec]
Since versions numbers don’t always line up such that it’s clear what is compatible with what, here’s a view of which JSTL versions go with which JSP versions:
JSTL 1.0>JSP 1.3; JSTL 1.1>JSP 2.0; JSTL 1.2>JSP 2.1;
Now let’s compare the specs for the empty operator
From the JSTL 1.0 Spec:
To evaluate empty A:
- If A is null, return true,
- Otherwise, if A is the empty string, then return true.
- Otherwise, if A is an empty array, then return true.
- Otherwise, if A is an empty Map, return true
- Otherwise, if A is an empty List, return true,
- Otherwise return false.
From the JSP 2.0 and 2.1 Spec, which are identical in this regard:
To evaluate empty A:
- If A is null, return true,
- Otherwise, if A is the empty string, then return true.
- Otherwise, if A is an empty array, then return true.
- Otherwise, if A is an empty Map, return true
- Otherwise, if A is an empty Collection, return true,
- Otherwise return false.
The difference is the last comparison. The older version uses the List interface, the newer uses Collection interface. List is a sub-interface of Collection, so anything that’s a Collection but not also a List will not work in JSTL 1.0. For example:
- The “empty” keyword in JSTL will work for things like List, ArrayList, LinkedList, Vector, etc.
- The “empty” keyword in JSTL will not work for Set, HashSet, TreeSet, etc.
REFACTOR
There’s 2 options around this.
1: upgrade.
2: If that’s not an option, there is another workaround.
The nice thing about “empty” is that it will check for both null and empty lists for you. Our workaround had to be a little more verbose:
<c:if test="${aquarium.fish != null && !aquarium.fish['empty']}">
<!---- feed the fish or something ---->
</c:if>