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 }