Custom tags are useful when either you need to introduce scripting variables and scoped attributes into the page (okay, you could do this with other actions, but how do you think they work anyway?!) or when you want to process body content (e.g. conditionals like the JSTL <c:if> and <c:choose>).
EL functions are supposed to be quite light-weight with no side-effects. On the contrary, tags almost always do have side-effects such as modifying or created scoped attributes, outputting body content (or not), or even updating databases (see the JSTL SQL actions).
There are a few cases where this is overlap, such as when you use a custom tag like the JSTL <fmt:formatDate /> to convert a Date into a
String. In this case you could use either EL or the tag... but EL functions should ideally deal with small amounts of data: counting elements in a list, doing some simple sums, generating a single string. It would be quite inappropriate to generate an entire XML DOM tree in an EL function for example, but quite acceptable in a tag.
Bear in mind also that data emitted from an EL function may be supplied as an attribute value, while tag data rarely is (although this is possible through <
jsp:attribute>); you typically don't want lots of data sent to an attribute.
These are only guidelines, but some really firm reasons are that EL cannot generate scripting variables or handle page content terribly well. These are jobs much better left to tags.
[ July 27, 2006: Message edited by: Charles Lyons ]