JSP, JSTL & the View Layer

The Expression Language (EL)

18 min Lesson 4 of 13

The Expression Language (EL)

Before Expression Language (EL) existed, JSP developers printed bean properties by writing scriptlets like <%= ((User) request.getAttribute("user")).getName() %>. That approach mixed Java casting and method calls directly into the page, making templates fragile and hard to read. EL was introduced in JSTL 1.0 and then standardised as part of the Servlet/JSP specification (Jakarta EL, currently 5.0). Today it is the only way you should output dynamic data in a JSP view.

The ${...} Syntax — Fundamentals

Every EL expression is wrapped in ${ ... }. At page-render time the container evaluates the expression and writes the result as a String. If the result is null, EL silently outputs an empty string — it does not throw a NullPointerException.

<!-- Outputs the string "Welcome, Alice!" if user.name is "Alice" --> <p>Welcome, ${user.name}!</p> <!-- Arithmetic --> <p>Total with tax: ${order.subtotal * 1.08}</p> <!-- String concatenation (EL 3.0+) --> <p>${firstName += ' ' += lastName}</p>
Deferred evaluation — #{...}: You may also encounter #{ ... } in Jakarta Faces (JSF). In plain JSP, stick to ${ ... }. The two forms behave identically in read-only EL contexts inside JSP, but #{ } carries special meaning in component frameworks.

How EL Resolves Names — The Scope Chain

When EL encounters a bare name like ${product} it searches for an attribute named product in this order: page → request → session → application. The first match wins. This mirrors the implicit objects you set with request.setAttribute(), session.setAttribute(), etc. You can be explicit by using the built-in scope maps:

${pageScope.product} <!-- only page scope --> ${requestScope.product} <!-- only request scope --> ${sessionScope.cart} <!-- only session scope --> ${applicationScope.config} <!-- only application scope -->

Always prefer the explicit form when the same name could reasonably appear in multiple scopes — it makes the page self-documenting and avoids subtle shadowing bugs.

Reading Bean Properties

EL uses the dot operator to navigate JavaBean properties. ${user.name} calls user.getName() — EL follows the JavaBeans convention (strip the property name, capitalise, prepend "get"). Boolean properties use is: ${product.available} calls product.isAvailable().

// Servlet — puts a bean into request scope User user = userService.findById(id); request.setAttribute("user", user); // JSP — reads the bean with EL <p>Name: ${user.name}</p> <p>Email: ${user.email}</p> <p>Admin? ${user.admin}</p> <!-- calls user.isAdmin() --> <p>Company: ${user.address.company}</p> <!-- chained navigation -->

Chained navigation (user.address.company) keeps traversing as long as each step returns a non-null object. A null anywhere in the chain stops traversal and returns an empty string rather than throwing.

The Bracket Operator — Collections and Dynamic Keys

The bracket operator ${expr[key]} is more powerful than the dot operator because the key can itself be an expression. It works on maps, lists, arrays, and beans:

<!-- Map access --> <p>${settings['theme']}</p> <p>${settings[preferredKey]}</p> <!-- dynamic key from another attribute --> <!-- List / array access by index --> <p>First tag: ${post.tags[0]}</p> <p>Last tag: ${post.tags[post.tags.size() - 1]}</p> <!-- EL 3.0 method calls --> <!-- Bean property via string (equivalent to dot) --> <p>${user['name']}</p>
Dot vs bracket: Use the dot operator for fixed, known property names — it reads more naturally. Use brackets when the key is dynamic, numeric (array/list index), or contains characters that are invalid in EL identifiers (hyphens, spaces).

Operators Inside EL Expressions

EL supports a rich set of operators so you can compute values and form conditions directly in the page without delegating to scriptlets.

  • Arithmetic: +, -, *, / (or div), % (or mod)
  • Relational: == (or eq), != (or ne), < (lt), > (gt), <= (le), >= (ge)
  • Logical: && (or and), || (or or), ! (or not)
  • Ternary: condition ? valueIfTrue : valueIfFalse
  • Empty test: empty expr — true if the value is null, an empty string, an empty array, or an empty collection
  • Assignment (EL 3.0+): = — rarely used in plain JSP pages, but available
<!-- Conditional CSS class --> <tr class="${item.outOfStock ? 'disabled' : 'active'}"> <!-- Safe empty check --> <c:if test="${not empty errors}"> <div class="alert">Please correct the errors below.</div> </c:if> <!-- Keyword aliases avoid escaping in attributes --> <c:if test="${count gt 0 and count lt 100}">...</c:if>
Avoid complex logic in EL. EL was designed for reading model data, not for running business logic. If an expression spans more than one short ternary, the logic belongs in the Servlet or a helper class, not the template. Keep views readable and testable.

EL Implicit Objects

EL exposes a set of read-only maps as implicit objects, analogous to the scriptlet implicit variables but more convenient:

  • param — request parameter by name, e.g. ${param.q}
  • paramValues — multi-value request parameters (returns String[])
  • header / headerValues — HTTP request headers
  • cookie — a map of Cookie objects; use ${cookie.JSESSIONID.value}
  • initParam — servlet context init parameters from web.xml
  • pageContext — the full PageContext object, giving access to the request, response, session, and more
<!-- Read a query-string parameter safely --> <p>You searched for: ${param.q}</p> <!-- Current user from session --> <p>Logged in as: ${sessionScope.currentUser.displayName}</p> <!-- Request URI via pageContext --> <p>Path: ${pageContext.request.requestURI}</p>

Calling Static Methods and Constructors (EL 3.0+)

Jakarta EL 3.0 (Servlet 3.1 / Tomcat 8+) allows calling static methods if you import the class first using the importClass or importStatic APIs, or via the <%@ page import %> plus EL static imports. In practice the most common usage is with utility functions registered through a JSTL function library — covered in the next lesson. For now, know that EL can call String.format-style utilities when properly wired, but that wiring lives in the tag library, not raw EL.

Composing EL with HTML Attributes

EL expressions compose seamlessly inside HTML attribute values. This is where they shine compared to scriptlets:

<a href="/orders/${order.id}/detail">View Order</a> <img src="${pageContext.request.contextPath}/images/${product.imageFile}" alt="${product.name}"> <input type="text" name="username" value="${fn:escapeXml(param.username)}">
Always escape user-supplied values with fn:escapeXml() when rendering them back into HTML attributes or content. EL does not escape output by default. Rendering ${param.search} directly into an attribute is an XSS vulnerability. The fn:escapeXml function is part of the JSTL Functions library, covered in Lesson 6.

Summary

Expression Language gives JSP pages a clean, null-safe way to read scoped attributes, navigate bean properties, access collections by index or key, and compute simple conditions — all without a single line of Java scriptlet. The scope chain (page → request → session → application) and explicit scope maps give you precise control over where data comes from. Use the dot operator for beans, brackets for maps and lists, the empty keyword for null-safe checks, and keyword aliases (lt, gt, and) to keep attribute values valid HTML. In the next lesson you will layer JSTL Core Tags on top of EL to handle iteration and branching declaratively.