0
# Exception Handling
1
2
FreeMarker provides a comprehensive exception handling system that helps identify and manage errors during template processing, parsing, and model operations.
3
4
## Core Exception Classes
5
6
### Base Template Exception
7
8
```java { .api }
9
class TemplateException extends Exception {
10
// Constructors
11
TemplateException(String description, Environment env);
12
TemplateException(String description, Environment env, Throwable cause);
13
TemplateException(Throwable cause, Environment env);
14
15
// Template context information
16
String getFTLInstructionStack();
17
int getLineNumber();
18
int getColumnNumber();
19
String getTemplateName();
20
Environment getEnvironment();
21
22
// Exception details
23
String getDescription();
24
Throwable getCause();
25
String getBlamedExpressionString();
26
String getMessageWithoutStackTop();
27
28
// Stack trace utilities
29
void printStackTrace(PrintWriter pw);
30
void printStackTrace(PrintStream ps);
31
}
32
```
33
34
### Template Model Exception
35
36
Exception for template model operations:
37
38
```java { .api }
39
class TemplateModelException extends TemplateException {
40
// Constructors
41
TemplateModelException(String description);
42
TemplateModelException(String description, Throwable cause);
43
TemplateModelException(Throwable cause);
44
45
// Create with environment context when available
46
TemplateModelException(String description, Environment env);
47
TemplateModelException(String description, Environment env, Throwable cause);
48
}
49
```
50
51
### Template Not Found Exception
52
53
Exception when templates cannot be located:
54
55
```java { .api }
56
class TemplateNotFoundException extends IOException {
57
// Constructors
58
TemplateNotFoundException(String templateName, Object customLookupCondition, String reason);
59
TemplateNotFoundException(String templateName, Object customLookupCondition, String reason, Throwable cause);
60
61
// Template information
62
String getTemplateName();
63
Object getCustomLookupCondition();
64
}
65
```
66
67
### Malformed Template Name Exception
68
69
Exception for invalid template names:
70
71
```java { .api }
72
class MalformedTemplateNameException extends IOException {
73
// Constructors
74
MalformedTemplateNameException(String templateName, String reason);
75
MalformedTemplateNameException(String templateName, String reason, Throwable cause);
76
77
// Template name information
78
String getTemplateName();
79
}
80
```
81
82
## Parsing Exceptions
83
84
### Parse Exception
85
86
Exception during template parsing:
87
88
```java { .api }
89
class ParseException extends IOException {
90
// Constructors (inherited from JavaCC ParseException)
91
ParseException(String message);
92
ParseException(Token currentTokenVal, int[][] expectedTokenSequences, String[] tokenImage);
93
ParseException();
94
95
// Parser context information
96
Token currentToken;
97
int[][] expectedTokenSequences;
98
String[] tokenImage;
99
100
// Position information
101
int getLineNumber();
102
int getColumnNumber();
103
String getTemplateName();
104
}
105
```
106
107
## Control Flow Exceptions
108
109
These exceptions are used internally for template control flow:
110
111
### Stop Exception
112
113
```java { .api }
114
class StopException extends TemplateException {
115
StopException(Environment env);
116
StopException(String description, Environment env);
117
}
118
```
119
120
### Return Exception
121
122
```java { .api }
123
class ReturnException extends TemplateException {
124
ReturnException(TemplateModel returnValue, Environment env);
125
TemplateModel getReturnValue();
126
}
127
```
128
129
### Break and Continue Exceptions
130
131
```java { .api }
132
class BreakException extends TemplateException {
133
BreakException(Environment env);
134
}
135
136
class ContinueException extends TemplateException {
137
ContinueException(Environment env);
138
}
139
```
140
141
## Exception Handlers
142
143
### Template Exception Handler Interface
144
145
```java { .api }
146
interface TemplateExceptionHandler {
147
void handleTemplateException(TemplateException te, Environment env, Writer out)
148
throws TemplateException;
149
}
150
```
151
152
### Built-in Exception Handlers
153
154
```java { .api }
155
// Predefined exception handlers in TemplateExceptionHandler
156
static final TemplateExceptionHandler IGNORE_HANDLER = IgnoreTemplateExceptionHandler.INSTANCE;
157
static final TemplateExceptionHandler DEBUG_HANDLER = DebugTemplateExceptionHandler.INSTANCE;
158
static final TemplateExceptionHandler HTML_DEBUG_HANDLER = HtmlDebugTemplateExceptionHandler.INSTANCE;
159
static final TemplateExceptionHandler RETHROW_HANDLER = RethrowTemplateExceptionHandler.INSTANCE;
160
```
161
162
#### Ignore Handler
163
164
Silently ignores exceptions and continues processing:
165
166
```java
167
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
168
// Exceptions are logged but don't interrupt template processing
169
```
170
171
#### Debug Handler
172
173
Prints exception information to the output:
174
175
```java
176
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
177
// Output includes: [ERROR: expression_that_failed]
178
```
179
180
#### HTML Debug Handler
181
182
Prints HTML-escaped exception information:
183
184
```java
185
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
186
// Safe for HTML output: <ERROR: expression_that_failed>
187
```
188
189
#### Rethrow Handler (Recommended)
190
191
Re-throws exceptions for proper error handling:
192
193
```java
194
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
195
// Exceptions are re-thrown to be handled by application code
196
```
197
198
### Custom Exception Handlers
199
200
```java
201
public class CustomExceptionHandler implements TemplateExceptionHandler {
202
private static final Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
203
204
@Override
205
public void handleTemplateException(TemplateException te, Environment env, Writer out)
206
throws TemplateException {
207
208
// Log the exception with context
209
logger.error("Template processing error in template '{}' at line {}: {}",
210
te.getTemplateName(), te.getLineNumber(), te.getMessage(), te);
211
212
try {
213
// Write user-friendly error message to output
214
out.write("<!-- Error occurred. See logs for details. -->");
215
216
// In development mode, include more details
217
if (isDevelopmentMode()) {
218
out.write("\n<!-- ");
219
out.write("Template: " + te.getTemplateName());
220
out.write(", Line: " + te.getLineNumber());
221
out.write(", Error: " + te.getMessage());
222
out.write(" -->");
223
}
224
} catch (IOException e) {
225
throw new TemplateException("Failed to write error message", env, e);
226
}
227
}
228
229
private boolean isDevelopmentMode() {
230
// Implementation depends on your application
231
return "development".equals(System.getProperty("app.mode"));
232
}
233
}
234
235
// Usage
236
cfg.setTemplateExceptionHandler(new CustomExceptionHandler());
237
```
238
239
## Attempt Exception Reporter
240
241
Handle exceptions from the `#attempt` directive:
242
243
```java { .api }
244
interface AttemptExceptionReporter {
245
void report(TemplateException te, Environment env);
246
}
247
248
// Built-in reporters
249
static final AttemptExceptionReporter LOG_ERROR_REPORTER = LoggingAttemptExceptionReporter.ERROR_REPORTER;
250
static final AttemptExceptionReporter LOG_WARN_REPORTER = LoggingAttemptExceptionReporter.WARN_REPORTER;
251
```
252
253
### Custom Attempt Exception Reporter
254
255
```java
256
public class CustomAttemptExceptionReporter implements AttemptExceptionReporter {
257
private static final Logger logger = LoggerFactory.getLogger(CustomAttemptExceptionReporter.class);
258
259
@Override
260
public void report(TemplateException te, Environment env) {
261
// Log attempt failures with context
262
logger.warn("Attempt directive failed in template '{}' at line {}: {}",
263
te.getTemplateName(), te.getLineNumber(), te.getMessage());
264
265
// Could also send to error tracking service
266
ErrorTracker.recordAttemptFailure(te.getTemplateName(), te.getMessage());
267
}
268
}
269
270
// Configuration
271
cfg.setAttemptExceptionReporter(new CustomAttemptExceptionReporter());
272
```
273
274
## Exception Handling Best Practices
275
276
### Application-Level Exception Handling
277
278
```java
279
public class TemplateProcessor {
280
private final Configuration config;
281
282
public String processTemplate(String templateName, Object dataModel) throws ProcessingException {
283
try {
284
Template template = config.getTemplate(templateName);
285
StringWriter out = new StringWriter();
286
template.process(dataModel, out);
287
return out.toString();
288
289
} catch (TemplateNotFoundException e) {
290
throw new ProcessingException("Template not found: " + e.getTemplateName(), e);
291
292
} catch (MalformedTemplateNameException e) {
293
throw new ProcessingException("Invalid template name: " + e.getTemplateName(), e);
294
295
} catch (ParseException e) {
296
throw new ProcessingException(
297
String.format("Template parsing failed at line %d, column %d: %s",
298
e.getLineNumber(), e.getColumnNumber(), e.getMessage()), e);
299
300
} catch (TemplateException e) {
301
throw new ProcessingException(
302
String.format("Template processing failed in '%s' at line %d: %s",
303
e.getTemplateName(), e.getLineNumber(), e.getMessage()), e);
304
305
} catch (IOException e) {
306
throw new ProcessingException("I/O error during template processing", e);
307
}
308
}
309
}
310
```
311
312
### Template-Level Error Handling
313
314
```ftl
315
<#-- Handle missing variables gracefully -->
316
<h1>${title!"Default Title"}</h1>
317
318
<#-- Use attempt directive for risky operations -->
319
<#attempt>
320
<#include "optional-content.ftl">
321
<#recover>
322
<p>Optional content not available.</p>
323
</#attempt>
324
325
<#-- Safe property access -->
326
<#if user??>
327
Welcome, ${user.name!"Anonymous"}!
328
<#else>
329
Please log in.
330
</#if>
331
332
<#-- Safe method calls -->
333
<#if utils?? && utils.formatDate??>
334
Today: ${utils.formatDate(date, "yyyy-MM-dd")}
335
<#else>
336
Today: ${date?string}
337
</#if>
338
```
339
340
### Model Exception Handling
341
342
```java
343
public class SafeUserModel implements TemplateHashModel {
344
private final User user;
345
346
public SafeUserModel(User user) {
347
this.user = user;
348
}
349
350
@Override
351
public TemplateModel get(String key) throws TemplateModelException {
352
try {
353
switch (key) {
354
case "name":
355
return new SimpleScalar(user.getName() != null ? user.getName() : "");
356
case "email":
357
return new SimpleScalar(user.getEmail() != null ? user.getEmail() : "");
358
case "age":
359
return new SimpleNumber(user.getAge());
360
case "fullName":
361
return new SimpleScalar(getFullName());
362
default:
363
return null;
364
}
365
} catch (Exception e) {
366
throw new TemplateModelException("Error accessing user property: " + key, e);
367
}
368
}
369
370
private String getFullName() throws TemplateModelException {
371
try {
372
String firstName = user.getFirstName();
373
String lastName = user.getLastName();
374
375
if (firstName == null && lastName == null) {
376
return "";
377
} else if (firstName == null) {
378
return lastName;
379
} else if (lastName == null) {
380
return firstName;
381
} else {
382
return firstName + " " + lastName;
383
}
384
} catch (Exception e) {
385
throw new TemplateModelException("Error building full name", e);
386
}
387
}
388
389
@Override
390
public boolean isEmpty() throws TemplateModelException {
391
return user == null;
392
}
393
}
394
```
395
396
## Error Information and Debugging
397
398
### Exception Context Information
399
400
```java
401
try {
402
template.process(dataModel, out);
403
} catch (TemplateException e) {
404
// Get detailed error information
405
System.err.println("Template Name: " + e.getTemplateName());
406
System.err.println("Line Number: " + e.getLineNumber());
407
System.err.println("Column Number: " + e.getColumnNumber());
408
System.err.println("FTL Instruction Stack: " + e.getFTLInstructionStack());
409
System.err.println("Blamed Expression: " + e.getBlamedExpressionString());
410
411
// Print full context
412
e.printStackTrace();
413
}
414
```
415
416
### Development vs Production Error Handling
417
418
```java
419
public class EnvironmentAwareExceptionHandler implements TemplateExceptionHandler {
420
private final boolean isDevelopment;
421
422
public EnvironmentAwareExceptionHandler(boolean isDevelopment) {
423
this.isDevelopment = isDevelopment;
424
}
425
426
@Override
427
public void handleTemplateException(TemplateException te, Environment env, Writer out)
428
throws TemplateException {
429
430
if (isDevelopment) {
431
// Development: Show detailed error information
432
try {
433
out.write("\n<!-- TEMPLATE ERROR -->\n");
434
out.write("<!-- Template: " + te.getTemplateName() + " -->\n");
435
out.write("<!-- Line: " + te.getLineNumber() + ", Column: " + te.getColumnNumber() + " -->\n");
436
out.write("<!-- Error: " + te.getMessage() + " -->\n");
437
out.write("<!-- FTL Stack: " + te.getFTLInstructionStack() + " -->\n");
438
out.write("<!-- END TEMPLATE ERROR -->\n");
439
} catch (IOException e) {
440
throw new TemplateException("Failed to write debug information", env, e);
441
}
442
} else {
443
// Production: Log error and show generic message
444
logger.error("Template processing error", te);
445
try {
446
out.write("<!-- An error occurred while processing this section -->");
447
} catch (IOException e) {
448
throw new TemplateException("Failed to write error placeholder", env, e);
449
}
450
}
451
}
452
}
453
```
454
455
### Exception Monitoring and Alerting
456
457
```java
458
public class MonitoringExceptionHandler implements TemplateExceptionHandler {
459
private final TemplateExceptionHandler delegate;
460
private final MetricRegistry metrics;
461
private final Counter errorCounter;
462
463
public MonitoringExceptionHandler(TemplateExceptionHandler delegate, MetricRegistry metrics) {
464
this.delegate = delegate;
465
this.metrics = metrics;
466
this.errorCounter = metrics.counter("template.errors");
467
}
468
469
@Override
470
public void handleTemplateException(TemplateException te, Environment env, Writer out)
471
throws TemplateException {
472
473
// Record metrics
474
errorCounter.inc();
475
metrics.counter("template.errors.by-template", "template", te.getTemplateName()).inc();
476
477
// Record error details for monitoring
478
ErrorEvent event = new ErrorEvent()
479
.setTemplateName(te.getTemplateName())
480
.setLineNumber(te.getLineNumber())
481
.setErrorMessage(te.getMessage())
482
.setTimestamp(System.currentTimeMillis());
483
484
// Send to monitoring system
485
MonitoringService.recordError(event);
486
487
// Alert on critical errors
488
if (isCriticalError(te)) {
489
AlertingService.sendAlert("Critical template error", te.getMessage());
490
}
491
492
// Delegate to original handler
493
delegate.handleTemplateException(te, env, out);
494
}
495
496
private boolean isCriticalError(TemplateException te) {
497
// Define criteria for critical errors
498
return te.getTemplateName().startsWith("critical/") ||
499
te.getMessage().contains("database") ||
500
te.getMessage().contains("security");
501
}
502
}
503
```
504
505
## Configuration for Error Handling
506
507
### Comprehensive Error Handling Setup
508
509
```java
510
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
511
512
// Basic error handling configuration
513
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
514
cfg.setAttemptExceptionReporter(AttemptExceptionReporter.LOG_WARN_REPORTER);
515
516
// Control exception logging
517
cfg.setLogTemplateExceptions(false); // Handle logging in exception handler
518
cfg.setWrapUncheckedExceptions(true); // Wrap RuntimeExceptions in TemplateException
519
520
// Development vs Production configuration
521
if (isDevelopmentMode()) {
522
cfg.setTemplateExceptionHandler(new DevelopmentExceptionHandler());
523
} else {
524
cfg.setTemplateExceptionHandler(new ProductionExceptionHandler());
525
}
526
527
// Custom error handling for specific scenarios
528
cfg.setTemplateExceptionHandler(
529
new ChainedExceptionHandler(
530
new SecurityExceptionHandler(), // Handle security-related errors first
531
new DatabaseExceptionHandler(), // Handle database errors
532
new DefaultExceptionHandler() // Handle everything else
533
)
534
);
535
```
536
537
### Testing Exception Handling
538
539
```java
540
@Test
541
public void testTemplateExceptionHandling() {
542
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
543
TestExceptionHandler handler = new TestExceptionHandler();
544
cfg.setTemplateExceptionHandler(handler);
545
546
// Test with template that has undefined variable
547
StringTemplateLoader loader = new StringTemplateLoader();
548
loader.putTemplate("test.ftl", "${undefinedVariable}");
549
cfg.setTemplateLoader(loader);
550
551
try {
552
Template template = cfg.getTemplate("test.ftl");
553
template.process(new HashMap<>(), new StringWriter());
554
fail("Expected TemplateException");
555
} catch (TemplateException e) {
556
assertEquals("undefined variable: undefinedVariable", e.getMessage());
557
assertTrue(handler.wasExceptionHandled());
558
}
559
}
560
561
class TestExceptionHandler implements TemplateExceptionHandler {
562
private boolean exceptionHandled = false;
563
564
@Override
565
public void handleTemplateException(TemplateException te, Environment env, Writer out)
566
throws TemplateException {
567
exceptionHandled = true;
568
throw te; // Re-throw for test verification
569
}
570
571
public boolean wasExceptionHandled() {
572
return exceptionHandled;
573
}
574
}
575
```