Search code examples
jspforeachjstl

JSTL c:forEach not throwing IndexOutOfBoundException when end is greater than items size


I work on a simple form for editing user and their notification emails.

User could have 3 emails (0 to 3). Though I create 3 inputs like this:

<%
    List<String> notifEmails = // some code to get user's emails
    pageContext.setAttribute("notifEmails", notifEmails);
%>
<c:forEach begin="0" end="2" var="index">
    <li>
        <label>adresse ${index + 1} :</label>
        <input type="email" value="${notifEmails[index]}">
    </li>
</c:forEach>

I'm suprise to not have an IndexOutOfBoundsException when I have less than 3 emails for a user in this expression ${notifEmails[index]}. I try to found some explication on the web, but I find nothing about this case. I want to be sure to understand what happens here.

Does someone get the explanation?


Solution

  • I needed lots of debug but I find it. Its a fact when you read the specification that EL is a language that choose to throw exception only if there is no possible default value that could be used instead. In my case the expression ${notifEmails[index]} is resolved by ArrayELResolver in this method:

    public Object getValue(ELContext context, Object base, Object property) {
        Objects.requireNonNull(context);
    
        if (base != null && base.getClass().isArray()) {
            context.setPropertyResolved(base, property);
            int idx = coerce(property);
            if (idx < 0 || idx >= Array.getLength(base)) {
                return null;
            }
            return Array.get(base, idx);
        }
    
        return null;
    }
    

    If the index is not within the bounds of the array, EL return null as "I don't find the value".

    Then EL coerce null to a String and in the specification null must be coerce to an empty String.

    public static final String coerceToString(final ELContext ctx, final Object obj) {
    
        if (ctx != null) {
            boolean originalIsPropertyResolved = ctx.isPropertyResolved();
            try {
                Object result = ctx.getELResolver().convertToType(ctx, obj, String.class);
                if (ctx.isPropertyResolved()) {
                    return (String) result;
                }
            } finally {
                ctx.setPropertyResolved(originalIsPropertyResolved);
            }
        }
    
        if (obj == null) {
            return "";
        } else if (obj instanceof String) {
            return (String) obj;
        } else if (obj instanceof Enum<?>) {
            return ((Enum<?>) obj).name();
        } else {
            return obj.toString();
        }
    }
    

    So, this behavior follow the specification and is normal.

    (Sorry tomcat exception screen! :D )