or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caching-loading.mdcore-processing.mdexception-handling.mdextensions.mdindex.mdobject-wrapping.mdoutput-formats.mdtemplate-models.md

exception-handling.mddocs/

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

```