or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mderror-handling.mdevaluation-contexts.mdexpression-evaluation.mdindex.mdmethod-constructor-resolution.mdproperty-index-access.mdspel-configuration.mdtype-system-support.md

error-handling.mddocs/

0

# Error Handling

1

2

This document covers SpEL's comprehensive exception hierarchy and error handling mechanisms, including exception types, error reporting, and best practices for robust error handling.

3

4

## Exception Hierarchy

5

6

### ExpressionException (Base Class)

7

8

```java

9

public abstract class ExpressionException extends RuntimeException {

10

protected String expressionString;

11

protected int position = -1;

12

13

public ExpressionException(String message);

14

public ExpressionException(String message, Throwable cause);

15

public ExpressionException(int position, String message);

16

public ExpressionException(int position, String message, Throwable cause);

17

public ExpressionException(String expressionString, String message);

18

public ExpressionException(String expressionString, int position, String message);

19

20

public String getExpressionString();

21

public int getPosition();

22

23

public String toDetailedString();

24

public String getSimpleMessage();

25

}

26

```

27

{ .api }

28

29

The base class for all SpEL-related exceptions, providing context about the expression and position where the error occurred.

30

31

## Core Exception Types

32

33

### ParseException

34

35

```java

36

public class ParseException extends ExpressionException {

37

public ParseException(String message);

38

public ParseException(int position, String message);

39

public ParseException(int position, String message, Throwable cause);

40

public ParseException(String expressionString, int position, String message);

41

}

42

```

43

{ .api }

44

45

Thrown during expression parsing when the expression syntax is invalid.

46

47

### EvaluationException

48

49

```java

50

public class EvaluationException extends ExpressionException {

51

public EvaluationException(String message);

52

public EvaluationException(String message, Throwable cause);

53

public EvaluationException(int position, String message);

54

public EvaluationException(int position, String message, Throwable cause);

55

public EvaluationException(String expressionString, String message);

56

public EvaluationException(String expressionString, int position, String message);

57

}

58

```

59

{ .api }

60

61

Thrown during expression evaluation when runtime errors occur.

62

63

### ExpressionInvocationTargetException

64

65

```java

66

public class ExpressionInvocationTargetException extends EvaluationException {

67

public ExpressionInvocationTargetException(int position, String message, Throwable cause);

68

public ExpressionInvocationTargetException(String expressionString, String message, Throwable cause);

69

}

70

```

71

{ .api }

72

73

Wraps exceptions thrown by methods or constructors invoked during expression evaluation.

74

75

### AccessException

76

77

```java

78

public class AccessException extends Exception {

79

public AccessException(String message);

80

public AccessException(String message, Exception cause);

81

}

82

```

83

{ .api }

84

85

Thrown by accessors and resolvers when access operations fail.

86

87

## SpEL-Specific Exceptions

88

89

### SpelEvaluationException

90

91

```java

92

public class SpelEvaluationException extends EvaluationException {

93

private SpelMessage message;

94

private Object[] inserts;

95

96

public SpelEvaluationException(SpelMessage message, Object... inserts);

97

public SpelEvaluationException(int position, SpelMessage message, Object... inserts);

98

public SpelEvaluationException(String expressionString, int position, SpelMessage message, Object... inserts);

99

public SpelEvaluationException(Throwable cause, SpelMessage message, Object... inserts);

100

public SpelEvaluationException(int position, Throwable cause, SpelMessage message, Object... inserts);

101

102

public SpelMessage getMessageCode();

103

public Object[] getInserts();

104

public void setPosition(int position);

105

}

106

```

107

{ .api }

108

109

SpEL-specific evaluation exception with structured error messages.

110

111

### SpelParseException

112

113

```java

114

public class SpelParseException extends ParseException {

115

private SpelMessage message;

116

private Object[] inserts;

117

118

public SpelParseException(String expressionString, int position, SpelMessage message, Object... inserts);

119

public SpelParseException(int position, SpelMessage message, Object... inserts);

120

121

public SpelMessage getMessageCode();

122

public Object[] getInserts();

123

}

124

```

125

{ .api }

126

127

SpEL-specific parsing exception with structured error messages.

128

129

### SpelMessage Enum

130

131

```java

132

public enum SpelMessage {

133

TYPE_CONVERSION_ERROR,

134

CONSTRUCTOR_NOT_FOUND,

135

METHOD_NOT_FOUND,

136

PROPERTY_OR_FIELD_NOT_READABLE,

137

PROPERTY_OR_FIELD_NOT_WRITABLE,

138

METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,

139

CANNOT_INDEX_INTO_NULL_VALUE,

140

NOT_COMPARABLE,

141

INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,

142

INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR,

143

INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR,

144

FUNCTION_REFERENCE_CANNOT_BE_INVOKED,

145

EXCEPTION_DURING_CONSTRUCTOR_INVOCATION,

146

EXCEPTION_DURING_METHOD_INVOCATION,

147

OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES,

148

PROBLEM_LOCATING_TYPE,

149

MISSING_CONSTRUCTOR_ARGS,

150

RUN_OUT_OF_STACK,

151

MAX_REPEATED_TEXT_SIZE_EXCEEDED,

152

// ... many more specific error codes

153

154

public String formatMessage(Object... inserts);

155

}

156

```

157

{ .api }

158

159

Enumeration of specific SpEL error messages with parameter formatting support.

160

161

## Error Handling Examples

162

163

### Basic Exception Handling

164

165

```java

166

ExpressionParser parser = new SpelExpressionParser();

167

EvaluationContext context = new StandardEvaluationContext();

168

169

// Handling parse exceptions

170

try {

171

Expression exp = parser.parseExpression("invalid)syntax");

172

} catch (ParseException e) {

173

System.err.println("Parse error at position " + e.getPosition() + ": " + e.getMessage());

174

System.err.println("Expression: " + e.getExpressionString());

175

System.err.println("Detailed: " + e.toDetailedString());

176

}

177

178

// Handling evaluation exceptions

179

try {

180

Expression exp = parser.parseExpression("nonExistentProperty");

181

Object result = exp.getValue(context, new Object());

182

} catch (EvaluationException e) {

183

System.err.println("Evaluation error: " + e.getMessage());

184

if (e.getPosition() != -1) {

185

System.err.println("At position: " + e.getPosition());

186

}

187

}

188

189

// Handling method invocation exceptions

190

try {

191

Expression exp = parser.parseExpression("toString().substring(-1)"); // Invalid substring index

192

String result = exp.getValue(context, "test", String.class);

193

} catch (ExpressionInvocationTargetException e) {

194

System.err.println("Method invocation failed: " + e.getMessage());

195

System.err.println("Root cause: " + e.getCause().getClass().getSimpleName());

196

}

197

```

198

{ .api }

199

200

### SpEL-Specific Exception Handling

201

202

```java

203

ExpressionParser parser = new SpelExpressionParser();

204

EvaluationContext context = new StandardEvaluationContext();

205

206

try {

207

Expression exp = parser.parseExpression("#unknownVariable.someMethod()");

208

Object result = exp.getValue(context);

209

} catch (SpelEvaluationException e) {

210

SpelMessage messageCode = e.getMessageCode();

211

Object[] inserts = e.getInserts();

212

213

switch (messageCode) {

214

case PROPERTY_OR_FIELD_NOT_READABLE:

215

System.err.println("Property not readable: " + inserts[0]);

216

break;

217

case METHOD_NOT_FOUND:

218

System.err.println("Method not found: " + inserts[0] + " on type " + inserts[1]);

219

break;

220

case TYPE_CONVERSION_ERROR:

221

System.err.println("Cannot convert from " + inserts[0] + " to " + inserts[1]);

222

break;

223

default:

224

System.err.println("SpEL error: " + e.getMessage());

225

}

226

}

227

```

228

{ .api }

229

230

### Detailed Error Reporting

231

232

```java

233

public class DetailedErrorReporter {

234

235

public static void reportError(ExpressionException e) {

236

System.err.println("=== Expression Error Report ===");

237

System.err.println("Error Type: " + e.getClass().getSimpleName());

238

System.err.println("Message: " + e.getMessage());

239

240

if (e.getExpressionString() != null) {

241

System.err.println("Expression: " + e.getExpressionString());

242

}

243

244

if (e.getPosition() != -1) {

245

System.err.println("Position: " + e.getPosition());

246

if (e.getExpressionString() != null) {

247

highlightErrorPosition(e.getExpressionString(), e.getPosition());

248

}

249

}

250

251

if (e instanceof SpelEvaluationException) {

252

SpelEvaluationException spel = (SpelEvaluationException) e;

253

System.err.println("Error Code: " + spel.getMessageCode());

254

System.err.println("Parameters: " + Arrays.toString(spel.getInserts()));

255

}

256

257

System.err.println("Detailed: " + e.toDetailedString());

258

259

if (e.getCause() != null) {

260

System.err.println("Root Cause: " + e.getCause().getClass().getSimpleName());

261

System.err.println("Root Message: " + e.getCause().getMessage());

262

}

263

264

System.err.println("=== End Report ===");

265

}

266

267

private static void highlightErrorPosition(String expression, int position) {

268

System.err.println("Position indicator:");

269

System.err.println(expression);

270

StringBuilder pointer = new StringBuilder();

271

for (int i = 0; i < position; i++) {

272

pointer.append(' ');

273

}

274

pointer.append('^');

275

System.err.println(pointer.toString());

276

}

277

}

278

279

// Usage

280

try {

281

Expression exp = parser.parseExpression("obj.badProperty");

282

exp.getValue(context);

283

} catch (ExpressionException e) {

284

DetailedErrorReporter.reportError(e);

285

}

286

```

287

{ .api }

288

289

## Robust Expression Evaluation

290

291

### Safe Expression Evaluator

292

293

```java

294

public class SafeExpressionEvaluator {

295

private final ExpressionParser parser;

296

private final EvaluationContext context;

297

298

public SafeExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {

299

this.parser = parser;

300

this.context = context;

301

}

302

303

public <T> Optional<T> evaluateSafely(String expression, Class<T> expectedType) {

304

return evaluateSafely(expression, null, expectedType);

305

}

306

307

public <T> Optional<T> evaluateSafely(String expression, Object rootObject, Class<T> expectedType) {

308

try {

309

Expression exp = parser.parseExpression(expression);

310

T result = exp.getValue(context, rootObject, expectedType);

311

return Optional.ofNullable(result);

312

} catch (ExpressionException e) {

313

logError(expression, e);

314

return Optional.empty();

315

}

316

}

317

318

public EvaluationResult evaluateWithResult(String expression, Object rootObject) {

319

try {

320

Expression exp = parser.parseExpression(expression);

321

Object result = exp.getValue(context, rootObject);

322

return EvaluationResult.success(result);

323

} catch (ExpressionException e) {

324

return EvaluationResult.failure(e);

325

}

326

}

327

328

private void logError(String expression, ExpressionException e) {

329

System.err.printf("Failed to evaluate expression '%s': %s%n", expression, e.getMessage());

330

}

331

332

public static class EvaluationResult {

333

private final Object value;

334

private final ExpressionException error;

335

private final boolean successful;

336

337

private EvaluationResult(Object value, ExpressionException error, boolean successful) {

338

this.value = value;

339

this.error = error;

340

this.successful = successful;

341

}

342

343

public static EvaluationResult success(Object value) {

344

return new EvaluationResult(value, null, true);

345

}

346

347

public static EvaluationResult failure(ExpressionException error) {

348

return new EvaluationResult(null, error, false);

349

}

350

351

public boolean isSuccessful() { return successful; }

352

public Object getValue() { return value; }

353

public ExpressionException getError() { return error; }

354

355

public <T> T getValueAs(Class<T> type) {

356

return successful ? type.cast(value) : null;

357

}

358

359

public Object getValueOrDefault(Object defaultValue) {

360

return successful ? value : defaultValue;

361

}

362

}

363

}

364

365

// Usage

366

SafeExpressionEvaluator evaluator = new SafeExpressionEvaluator(parser, context);

367

368

// Safe evaluation with Optional

369

Optional<String> name = evaluator.evaluateSafely("person.name", person, String.class);

370

if (name.isPresent()) {

371

System.out.println("Name: " + name.get());

372

} else {

373

System.out.println("Failed to get name");

374

}

375

376

// Evaluation with detailed result

377

EvaluationResult result = evaluator.evaluateWithResult("person.age * 2", person);

378

if (result.isSuccessful()) {

379

System.out.println("Double age: " + result.getValue());

380

} else {

381

System.err.println("Error: " + result.getError().getMessage());

382

}

383

```

384

{ .api }

385

386

### Expression Validation

387

388

```java

389

public class ExpressionValidator {

390

private final ExpressionParser parser;

391

392

public ExpressionValidator(ExpressionParser parser) {

393

this.parser = parser;

394

}

395

396

public ValidationResult validate(String expression) {

397

return validate(expression, null);

398

}

399

400

public ValidationResult validate(String expression, Class<?> expectedType) {

401

try {

402

// Check parsing

403

Expression exp = parser.parseExpression(expression);

404

405

// Additional validation checks

406

List<String> warnings = new ArrayList<>();

407

408

// Check expression length

409

if (expression.length() > 1000) {

410

warnings.add("Expression is very long (" + expression.length() + " characters)");

411

}

412

413

// Check for potentially dangerous patterns

414

if (expression.contains("T(java.lang.Runtime)")) {

415

return ValidationResult.invalid("Dangerous type reference detected");

416

}

417

418

if (expression.contains("getClass()")) {

419

warnings.add("Reflection access detected - may cause security issues");

420

}

421

422

// Check complexity (nesting depth)

423

int nestingDepth = calculateNestingDepth(expression);

424

if (nestingDepth > 10) {

425

warnings.add("High nesting depth (" + nestingDepth + ") may impact performance");

426

}

427

428

return ValidationResult.valid(warnings);

429

430

} catch (ParseException e) {

431

return ValidationResult.invalid(e.getMessage(), e.getPosition());

432

}

433

}

434

435

private int calculateNestingDepth(String expression) {

436

int depth = 0;

437

int maxDepth = 0;

438

439

for (char c : expression.toCharArray()) {

440

if (c == '(' || c == '[' || c == '{') {

441

depth++;

442

maxDepth = Math.max(maxDepth, depth);

443

} else if (c == ')' || c == ']' || c == '}') {

444

depth--;

445

}

446

}

447

448

return maxDepth;

449

}

450

451

public static class ValidationResult {

452

private final boolean valid;

453

private final String errorMessage;

454

private final int errorPosition;

455

private final List<String> warnings;

456

457

private ValidationResult(boolean valid, String errorMessage, int errorPosition, List<String> warnings) {

458

this.valid = valid;

459

this.errorMessage = errorMessage;

460

this.errorPosition = errorPosition;

461

this.warnings = warnings != null ? warnings : Collections.emptyList();

462

}

463

464

public static ValidationResult valid(List<String> warnings) {

465

return new ValidationResult(true, null, -1, warnings);

466

}

467

468

public static ValidationResult invalid(String errorMessage) {

469

return new ValidationResult(false, errorMessage, -1, null);

470

}

471

472

public static ValidationResult invalid(String errorMessage, int position) {

473

return new ValidationResult(false, errorMessage, position, null);

474

}

475

476

public boolean isValid() { return valid; }

477

public String getErrorMessage() { return errorMessage; }

478

public int getErrorPosition() { return errorPosition; }

479

public List<String> getWarnings() { return warnings; }

480

481

public boolean hasWarnings() { return !warnings.isEmpty(); }

482

}

483

}

484

485

// Usage

486

ExpressionValidator validator = new ExpressionValidator(parser);

487

488

ValidationResult result = validator.validate("person.name.toUpperCase()");

489

if (result.isValid()) {

490

System.out.println("Expression is valid");

491

if (result.hasWarnings()) {

492

System.out.println("Warnings: " + result.getWarnings());

493

}

494

} else {

495

System.err.println("Invalid expression: " + result.getErrorMessage());

496

if (result.getErrorPosition() != -1) {

497

System.err.println("At position: " + result.getErrorPosition());

498

}

499

}

500

```

501

{ .api }

502

503

## Error Recovery Strategies

504

505

### Fallback Expression Evaluator

506

507

```java

508

public class FallbackExpressionEvaluator {

509

private final ExpressionParser parser;

510

private final EvaluationContext context;

511

private final Map<String, Object> fallbackValues = new HashMap<>();

512

513

public FallbackExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {

514

this.parser = parser;

515

this.context = context;

516

}

517

518

public void setFallbackValue(String expression, Object fallbackValue) {

519

fallbackValues.put(expression, fallbackValue);

520

}

521

522

public Object evaluate(String expression, Object rootObject) {

523

try {

524

Expression exp = parser.parseExpression(expression);

525

return exp.getValue(context, rootObject);

526

} catch (ExpressionException e) {

527

Object fallback = fallbackValues.get(expression);

528

if (fallback != null) {

529

System.out.printf("Using fallback value for expression '%s': %s%n",

530

expression, fallback);

531

return fallback;

532

}

533

534

// Try simplified version of the expression

535

Object simplifiedResult = trySimplifiedExpression(expression, rootObject, e);

536

if (simplifiedResult != null) {

537

return simplifiedResult;

538

}

539

540

throw e; // Re-throw if no recovery possible

541

}

542

}

543

544

private Object trySimplifiedExpression(String expression, Object rootObject, ExpressionException originalError) {

545

// Try removing method calls and just accessing properties

546

if (expression.contains(".")) {

547

String[] parts = expression.split("\\.");

548

if (parts.length > 1) {

549

String simpler = parts[0] + "." + parts[1]; // Take first two parts

550

try {

551

Expression exp = parser.parseExpression(simpler);

552

Object result = exp.getValue(context, rootObject);

553

System.out.printf("Fallback to simplified expression '%s' from '%s'%n",

554

simpler, expression);

555

return result;

556

} catch (ExpressionException e) {

557

// Ignore, will return null

558

}

559

}

560

}

561

562

return null;

563

}

564

}

565

566

// Usage

567

FallbackExpressionEvaluator evaluator = new FallbackExpressionEvaluator(parser, context);

568

evaluator.setFallbackValue("user.preferences.theme", "default");

569

evaluator.setFallbackValue("user.profile.avatar", "/images/default-avatar.png");

570

571

Object theme = evaluator.evaluate("user.preferences.theme", user);

572

// If evaluation fails, returns "default"

573

```

574

{ .api }

575

576

### Retry with Context Adjustment

577

578

```java

579

public class RetryingExpressionEvaluator {

580

private final ExpressionParser parser;

581

582

public Object evaluateWithRetry(String expression, EvaluationContext context, Object rootObject) {

583

try {

584

Expression exp = parser.parseExpression(expression);

585

return exp.getValue(context, rootObject);

586

} catch (SpelEvaluationException e) {

587

// Retry with adjusted context based on error type

588

EvaluationContext adjustedContext = adjustContextForError(context, e);

589

if (adjustedContext != null) {

590

try {

591

Expression exp = parser.parseExpression(expression);

592

return exp.getValue(adjustedContext, rootObject);

593

} catch (ExpressionException retryError) {

594

// Both attempts failed

595

throw new EvaluationException("Expression failed even after context adjustment: " +

596

retryError.getMessage(), retryError);

597

}

598

}

599

throw e; // No adjustment possible

600

}

601

}

602

603

private EvaluationContext adjustContextForError(EvaluationContext context, SpelEvaluationException e) {

604

SpelMessage messageCode = e.getMessageCode();

605

606

if (messageCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {

607

// Add more lenient property accessor

608

if (context instanceof StandardEvaluationContext) {

609

StandardEvaluationContext standardContext = (StandardEvaluationContext) context;

610

StandardEvaluationContext newContext = new StandardEvaluationContext(standardContext.getRootObject());

611

612

// Copy existing configuration

613

newContext.setPropertyAccessors(standardContext.getPropertyAccessors());

614

newContext.setMethodResolvers(standardContext.getMethodResolvers());

615

616

// Add lenient accessor

617

newContext.addPropertyAccessor(new LenientPropertyAccessor());

618

return newContext;

619

}

620

}

621

622

if (messageCode == SpelMessage.METHOD_NOT_FOUND) {

623

// Add method resolver that provides default implementations

624

if (context instanceof StandardEvaluationContext) {

625

StandardEvaluationContext standardContext = (StandardEvaluationContext) context;

626

StandardEvaluationContext newContext = new StandardEvaluationContext(standardContext.getRootObject());

627

628

newContext.setPropertyAccessors(standardContext.getPropertyAccessors());

629

newContext.setMethodResolvers(standardContext.getMethodResolvers());

630

newContext.addMethodResolver(new DefaultMethodResolver());

631

return newContext;

632

}

633

}

634

635

return null; // No adjustment available

636

}

637

}

638

```

639

{ .api }

640

641

## Custom Exception Handling

642

643

### Domain-Specific Exception Wrapper

644

645

```java

646

public class BusinessExpressionEvaluator {

647

private final ExpressionParser parser;

648

private final EvaluationContext context;

649

650

public BusinessExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {

651

this.parser = parser;

652

this.context = context;

653

}

654

655

public Object evaluateBusinessRule(String ruleName, String expression, Object businessObject)

656

throws BusinessRuleException {

657

658

try {

659

Expression exp = parser.parseExpression(expression);

660

return exp.getValue(context, businessObject);

661

} catch (ParseException e) {

662

throw new BusinessRuleException(

663

"Invalid business rule syntax in rule '" + ruleName + "'",

664

e, BusinessRuleException.ErrorType.SYNTAX_ERROR

665

);

666

} catch (SpelEvaluationException e) {

667

SpelMessage messageCode = e.getMessageCode();

668

669

BusinessRuleException.ErrorType errorType = switch (messageCode) {

670

case PROPERTY_OR_FIELD_NOT_READABLE -> BusinessRuleException.ErrorType.MISSING_DATA;

671

case METHOD_NOT_FOUND -> BusinessRuleException.ErrorType.INVALID_OPERATION;

672

case TYPE_CONVERSION_ERROR -> BusinessRuleException.ErrorType.DATA_TYPE_MISMATCH;

673

default -> BusinessRuleException.ErrorType.EVALUATION_ERROR;

674

};

675

676

throw new BusinessRuleException(

677

"Business rule '" + ruleName + "' evaluation failed: " + e.getMessage(),

678

e, errorType

679

);

680

} catch (Exception e) {

681

throw new BusinessRuleException(

682

"Unexpected error in business rule '" + ruleName + "'",

683

e, BusinessRuleException.ErrorType.SYSTEM_ERROR

684

);

685

}

686

}

687

}

688

689

public class BusinessRuleException extends Exception {

690

public enum ErrorType {

691

SYNTAX_ERROR,

692

MISSING_DATA,

693

INVALID_OPERATION,

694

DATA_TYPE_MISMATCH,

695

EVALUATION_ERROR,

696

SYSTEM_ERROR

697

}

698

699

private final ErrorType errorType;

700

private final String ruleName;

701

702

public BusinessRuleException(String message, Throwable cause, ErrorType errorType) {

703

super(message, cause);

704

this.errorType = errorType;

705

this.ruleName = extractRuleNameFromMessage(message);

706

}

707

708

public ErrorType getErrorType() { return errorType; }

709

public String getRuleName() { return ruleName; }

710

711

private String extractRuleNameFromMessage(String message) {

712

// Extract rule name from error message

713

int start = message.indexOf("'");

714

int end = message.indexOf("'", start + 1);

715

return (start != -1 && end != -1) ? message.substring(start + 1, end) : "unknown";

716

}

717

}

718

```

719

{ .api }

720

721

## Best Practices

722

723

### Exception Handling Guidelines

724

725

1. **Catch Specific Exceptions**: Catch the most specific exception types first

726

2. **Preserve Context**: Include expression string and position in error messages

727

3. **Log Appropriately**: Use different log levels for different exception types

728

4. **Provide Fallbacks**: Implement graceful degradation when possible

729

5. **Validate Early**: Validate expressions at configuration time, not runtime

730

6. **Use Structured Errors**: Leverage SpelMessage enum for consistent error handling

731

732

### Error Prevention Strategies

733

734

```java

735

public class RobustExpressionService {

736

private final ExpressionParser parser;

737

private final ExpressionValidator validator;

738

private final Map<String, Expression> compiledExpressions = new ConcurrentHashMap<>();

739

740

public RobustExpressionService() {

741

// Use configuration that helps prevent errors

742

SpelParserConfiguration config = new SpelParserConfiguration(

743

SpelCompilerMode.IMMEDIATE, // Compile for better error detection

744

getClass().getClassLoader(),

745

true, // Auto-grow null references to prevent NPEs

746

true, // Auto-grow collections

747

100, // Reasonable auto-grow limit

748

10000 // Expression length limit

749

);

750

751

this.parser = new SpelExpressionParser(config);

752

this.validator = new ExpressionValidator(parser);

753

}

754

755

public void registerExpression(String name, String expressionString) throws InvalidExpressionException {

756

// Validate before storing

757

ValidationResult validation = validator.validate(expressionString);

758

if (!validation.isValid()) {

759

throw new InvalidExpressionException(

760

"Invalid expression '" + name + "': " + validation.getErrorMessage(),

761

validation.getErrorPosition()

762

);

763

}

764

765

// Compile and store

766

try {

767

Expression expression = parser.parseExpression(expressionString);

768

compiledExpressions.put(name, expression);

769

} catch (ParseException e) {

770

throw new InvalidExpressionException(

771

"Failed to compile expression '" + name + "': " + e.getMessage(),

772

e.getPosition()

773

);

774

}

775

}

776

777

public Object evaluate(String name, EvaluationContext context, Object rootObject)

778

throws ExpressionNotFoundException, EvaluationException {

779

780

Expression expression = compiledExpressions.get(name);

781

if (expression == null) {

782

throw new ExpressionNotFoundException("No expression registered with name: " + name);

783

}

784

785

return expression.getValue(context, rootObject);

786

}

787

}

788

```

789

{ .api }

790

791

### Monitoring and Metrics

792

793

```java

794

public class ExpressionMetrics {

795

private final AtomicLong parseErrorCount = new AtomicLong();

796

private final AtomicLong evaluationErrorCount = new AtomicLong();

797

private final Map<SpelMessage, AtomicLong> spelErrorCounts = new ConcurrentHashMap<>();

798

799

public void recordParseError() {

800

parseErrorCount.incrementAndGet();

801

}

802

803

public void recordEvaluationError(ExpressionException e) {

804

evaluationErrorCount.incrementAndGet();

805

806

if (e instanceof SpelEvaluationException) {

807

SpelEvaluationException spelEx = (SpelEvaluationException) e;

808

spelErrorCounts.computeIfAbsent(spelEx.getMessageCode(), k -> new AtomicLong())

809

.incrementAndGet();

810

}

811

}

812

813

public void printReport() {

814

System.out.println("Expression Error Metrics:");

815

System.out.println("Parse errors: " + parseErrorCount.get());

816

System.out.println("Evaluation errors: " + evaluationErrorCount.get());

817

818

if (!spelErrorCounts.isEmpty()) {

819

System.out.println("SpEL error breakdown:");

820

spelErrorCounts.entrySet().stream()

821

.sorted(Map.Entry.<SpelMessage, AtomicLong>comparingByValue((a, b) ->

822

Long.compare(b.get(), a.get())))

823

.forEach(entry ->

824

System.out.printf(" %s: %d%n", entry.getKey(), entry.getValue().get()));

825

}

826

}

827

}

828

```

829

{ .api }