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>
January 28, 2008 at 1:25 pm
I trust that no actual fish were harmed in the testing of this bug…?
January 28, 2008 at 4:04 pm
Of course not!
January 31, 2008 at 11:54 am
I just wanted to let you know that I used this post to determine which JSTL version I should use with JSP 2.0. Thanks for posting this!
January 31, 2008 at 1:37 pm
Hey great! It’s nice to know that it helped someone!
February 8, 2008 at 10:34 am
Wow, that’s freakin’ weird the spec used to specify List instead of Collection. Thanks for that, Gayle.
By the way, straight dotted notation should have also worked to call isEmpty on Set:
…
February 8, 2008 at 10:34 am
<c:if test=”${aquarium.fish != null && !aquarium.fish.empty}”>…
February 20, 2008 at 4:36 pm
Finally got a chance to try this, and I’m getting an error when I try Marc’s idea (which totally seems like it should work b/c I know HashSet has an isEmpty method). Something I’m missing?
jsp.error.tlv.invalid.page
5: tag = ‘if’ / attribute = ‘test’: An error occurred while parsing custom action attribute “test” with value “${aquarium.fish != null && !aquarium.fish.empty}”: Encountered “empty”, expected one of []
March 29, 2008 at 10:39 am
I ran into the same thing with a TreeSet. ‘empty’ seems to be treated as a keyword when using dotted notation.
Try this workaround, it worked for me:
June 26, 2008 at 10:56 am
That’s a cool solution, thanks for this posting.
September 15, 2008 at 7:19 am
thank Gayle for the post.
September 29, 2008 at 4:58 am
did you try to feed the bug to the fish?
January 5, 2010 at 11:27 am
Thank you
May 31, 2010 at 3:01 pm
I was having the same problem but using JSTL on JEE 5 (no idea which version of JSTL it is, i think it is 1.1).
As far as i have figured out, JSTL doesnt evaluate any functions on the parameters. It handles them as POJOs (only properties with setters and getters). But i found there is a function library on JSTL. Its very small, but helps to handle this problem, so check out this:
http://java.sun.com/javaee/5/docs/tutorial/doc/bnalg.html
Good luck!
September 16, 2011 at 11:44 am
I wish it worked on anything with an isEmpty method. It’s frustrating that you can’t use ${empty thing} or ${thing.empty} on a thing that has an empty method. The first doesn’t work because it does not have the right interface, the second is a syntax error because “empty” is a keyword in jstl.
September 18, 2012 at 2:57 pm
I am trying to check whether the Map is null or not using : and it is returning true even when the map is not empty. Tried doing – and it returns true as well. Is there a way to resolve this?