FreeMarker
template language
variable existence
programming
web development

How to check if a variable exists in a FreeMarker template?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

To check if a variable exists in a FreeMarker template, use the ?? operator: <#if myVar??>. This is the modern, recommended approach. The older ?exists built-in still works but has been deprecated since FreeMarker 2.3.x. This article covers every technique for handling missing variables in FreeMarker, with practical examples and the pitfalls that trip up most developers.

The double question mark ?? is FreeMarker's "has content" test. It returns true if the variable exists and is not null.

freemarker
1<#if user??>
2    Welcome back, ${user.name}!
3<#else>
4    Please log in.
5</#if>

You can also use it inline with the then built-in (FreeMarker 2.3.23+):

freemarker
${user???then("Logged in", "Guest")}

Note the triple ? here: ?? is the existence check, and the third ? starts the then built-in.

Checking Nested Properties

For nested objects, ?? checks the entire chain. If any part is null or missing, it returns false without throwing an error:

freemarker
<#if user.address.city??>
    City: ${user.address.city}
</#if>

This is safe even if user.address itself is null. FreeMarker short-circuits the evaluation.

The ! Operator (Default Values)

The ! operator provides a default value when a variable is missing or null:

freemarker
${user.name!"Anonymous"}

If user.name does not exist or is null, "Anonymous" is used instead.

For a default empty string, use ! with no value:

freemarker
${user.bio!}

This outputs nothing if bio is missing, instead of throwing an error.

Default Values for Complex Types

You can provide defaults for any type, not just strings:

freemarker
1<#-- Default number -->
2${item.quantity!0}
3
4<#-- Default boolean -->
5<#if user.active!false>
6    Account is active
7</#if>
8
9<#-- Default list (empty sequence) -->
10<#list user.roles![] as role>
11    ${role}
12</#list>

The Deprecated ?exists Built-in

The ?exists operator works but is deprecated. You will see it in older codebases:

freemarker
1<#-- Deprecated - avoid in new code -->
2<#if myVariable?exists>
3    ${myVariable}
4</#if>
5
6<#-- Modern equivalent -->
7<#if myVariable??>
8    ${myVariable}
9</#if>

The same applies to ?if_exists, which is the default-value equivalent:

freemarker
1<#-- Deprecated -->
2${myVariable?if_exists}
3
4<#-- Modern equivalent -->
5${myVariable!}

The ?has_content Built-in

?has_content goes further than ??. It returns false for variables that exist but are "empty" (empty string, empty list, empty map, or null):

freemarker
1<#if user.bio?has_content>
2    <p>${user.bio}</p>
3<#else>
4    <p>No bio provided.</p>
5</#if>

Here is the difference between ?? and ?has_content:

Value of xx??x?has_content
"hello"truetrue
""truefalse
[] (empty list)truefalse
{} (empty map)truefalse
nullfalsefalse
not defined at allfalsefalse

Using <#attempt> / <#recover> for Error Handling

For situations where variable access might throw an error (not just be missing), use the attempt/recover block:

freemarker
1<#attempt>
2    ${someService.fetchData()}
3<#recover>
4    <p>Data temporarily unavailable.</p>
5</#attempt>

This catches any exception during evaluation, not just missing variables. Use it sparingly since it silently swallows errors. For simple existence checks, ?? and ! are better.

Practical Patterns

Pattern 1: Conditional CSS Class

freemarker
<div class="alert <#if error??>alert-danger<#else>alert-info</#if>">
    ${message!"No messages"}
</div>

Pattern 2: Safe Iteration Over Optional Lists

freemarker
1<ul>
2<#list items![] as item>
3    <li>${item.name!""} - ${item.price!0}</li>
4<#else>
5    <li>No items found.</li>
6</#list>
7</ul>

Pattern 3: Assigning a Default Then Reusing

freemarker
<#assign displayName = user.displayName!user.email!"Unknown User">
<h1>${displayName}</h1>
<p>Welcome, ${displayName}.</p>

Pattern 4: Checking Map Keys

freemarker
1<#if config["database.url"]??>
2    Connecting to ${config["database.url"]}
3<#else>
4    No database URL configured.
5</#if>

Comparison of All Methods

MethodSyntaxReturnsUse When
??var??true/falseChecking existence before use
!var!"default"Value or defaultProviding a fallback inline
?has_contentvar?has_contenttrue/falseMust be non-null AND non-empty
?existsvar?existstrue/falseLegacy code only (deprecated)
?if_existsvar?if_existsValue or emptyLegacy code only (deprecated)
attempt/recoverBlock syntaxN/ACatching runtime errors

Common Pitfalls

  • Using ?exists in new code. It works but is deprecated. Use ?? instead. Newer FreeMarker versions may log deprecation warnings that clutter your logs.
  • Forgetting parentheses with ! on method calls. ${foo.bar()!"default"} does not work as expected. You need ${(foo.bar())!"default"} with parentheses to scope the default correctly.
  • Confusing ?? with ?has_content for strings. An empty string "" passes the ?? check. If you need to treat empty strings as missing, use ?has_content.
  • Assuming ! works on the whole chain by default. ${user.address.city!"N/A"} fails if user itself is null. Use ${(user.address.city)!"N/A"} to make the default cover the entire expression. The parentheses are required.
  • Using attempt/recover for simple null checks. It catches all exceptions, masking real bugs. Reserve it for genuinely unpredictable operations.
  • Not setting null handling in FreeMarker configuration. In your Java config, consider setting template_exception_handler to a strict handler in development so missing variables surface immediately, then use ! and ?? intentionally in your templates.

Summary

  • Use ?? to check if a variable exists: <#if myVar??>.
  • Use ! to provide inline defaults: ${myVar!"fallback"}.
  • Use ?has_content when you need to reject empty strings, lists, and maps in addition to null.
  • Wrap complex expressions in parentheses when using ! to scope the default: ${(a.b.c)!"default"}.
  • Avoid deprecated ?exists and ?if_exists in new code.
  • Use attempt/recover only for catching runtime exceptions, not for routine null checks.

Course illustration
Course illustration

All Rights Reserved.