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 ?? Operator (Recommended)
The double question mark ?? is FreeMarker's "has content" test. It returns true if the variable exists and is not null.
You can also use it inline with the then built-in (FreeMarker 2.3.23+):
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:
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:
If user.name does not exist or is null, "Anonymous" is used instead.
For a default empty string, use ! with no value:
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:
The Deprecated ?exists Built-in
The ?exists operator works but is deprecated. You will see it in older codebases:
The same applies to ?if_exists, which is the default-value equivalent:
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):
Here is the difference between ?? and ?has_content:
Value of x | x?? | x?has_content |
"hello" | true | true |
"" | true | false |
[] (empty list) | true | false |
{} (empty map) | true | false |
null | false | false |
| not defined at all | false | false |
Using <#attempt> / <#recover> for Error Handling
For situations where variable access might throw an error (not just be missing), use the attempt/recover block:
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
Pattern 2: Safe Iteration Over Optional Lists
Pattern 3: Assigning a Default Then Reusing
Pattern 4: Checking Map Keys
Comparison of All Methods
| Method | Syntax | Returns | Use When |
?? | var?? | true/false | Checking existence before use |
! | var!"default" | Value or default | Providing a fallback inline |
?has_content | var?has_content | true/false | Must be non-null AND non-empty |
?exists | var?exists | true/false | Legacy code only (deprecated) |
?if_exists | var?if_exists | Value or empty | Legacy code only (deprecated) |
attempt/recover | Block syntax | N/A | Catching runtime errors |
Common Pitfalls
- Using
?existsin 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_contentfor 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 ifuseritself 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
nullhandling in FreeMarker configuration. In your Java config, consider settingtemplate_exception_handlerto 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_contentwhen 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
?existsand?if_existsin new code. - Use
attempt/recoveronly for catching runtime exceptions, not for routine null checks.

