Spring Expression Language (SpEL) provides a powerful expression language for querying and manipulating object graphs at runtime.
—
This document covers SpEL's comprehensive exception hierarchy and error handling mechanisms, including exception types, error reporting, and best practices for robust error handling.
public abstract class ExpressionException extends RuntimeException {
protected String expressionString;
protected int position = -1;
public ExpressionException(String message);
public ExpressionException(String message, Throwable cause);
public ExpressionException(int position, String message);
public ExpressionException(int position, String message, Throwable cause);
public ExpressionException(String expressionString, String message);
public ExpressionException(String expressionString, int position, String message);
public String getExpressionString();
public int getPosition();
public String toDetailedString();
public String getSimpleMessage();
}{ .api }
The base class for all SpEL-related exceptions, providing context about the expression and position where the error occurred.
public class ParseException extends ExpressionException {
public ParseException(String message);
public ParseException(int position, String message);
public ParseException(int position, String message, Throwable cause);
public ParseException(String expressionString, int position, String message);
}{ .api }
Thrown during expression parsing when the expression syntax is invalid.
public class EvaluationException extends ExpressionException {
public EvaluationException(String message);
public EvaluationException(String message, Throwable cause);
public EvaluationException(int position, String message);
public EvaluationException(int position, String message, Throwable cause);
public EvaluationException(String expressionString, String message);
public EvaluationException(String expressionString, int position, String message);
}{ .api }
Thrown during expression evaluation when runtime errors occur.
public class ExpressionInvocationTargetException extends EvaluationException {
public ExpressionInvocationTargetException(int position, String message, Throwable cause);
public ExpressionInvocationTargetException(String expressionString, String message, Throwable cause);
}{ .api }
Wraps exceptions thrown by methods or constructors invoked during expression evaluation.
public class AccessException extends Exception {
public AccessException(String message);
public AccessException(String message, Exception cause);
}{ .api }
Thrown by accessors and resolvers when access operations fail.
public class SpelEvaluationException extends EvaluationException {
private SpelMessage message;
private Object[] inserts;
public SpelEvaluationException(SpelMessage message, Object... inserts);
public SpelEvaluationException(int position, SpelMessage message, Object... inserts);
public SpelEvaluationException(String expressionString, int position, SpelMessage message, Object... inserts);
public SpelEvaluationException(Throwable cause, SpelMessage message, Object... inserts);
public SpelEvaluationException(int position, Throwable cause, SpelMessage message, Object... inserts);
public SpelMessage getMessageCode();
public Object[] getInserts();
public void setPosition(int position);
}{ .api }
SpEL-specific evaluation exception with structured error messages.
public class SpelParseException extends ParseException {
private SpelMessage message;
private Object[] inserts;
public SpelParseException(String expressionString, int position, SpelMessage message, Object... inserts);
public SpelParseException(int position, SpelMessage message, Object... inserts);
public SpelMessage getMessageCode();
public Object[] getInserts();
}{ .api }
SpEL-specific parsing exception with structured error messages.
public enum SpelMessage {
TYPE_CONVERSION_ERROR,
CONSTRUCTOR_NOT_FOUND,
METHOD_NOT_FOUND,
PROPERTY_OR_FIELD_NOT_READABLE,
PROPERTY_OR_FIELD_NOT_WRITABLE,
METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
CANNOT_INDEX_INTO_NULL_VALUE,
NOT_COMPARABLE,
INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR,
INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR,
FUNCTION_REFERENCE_CANNOT_BE_INVOKED,
EXCEPTION_DURING_CONSTRUCTOR_INVOCATION,
EXCEPTION_DURING_METHOD_INVOCATION,
OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES,
PROBLEM_LOCATING_TYPE,
MISSING_CONSTRUCTOR_ARGS,
RUN_OUT_OF_STACK,
MAX_REPEATED_TEXT_SIZE_EXCEEDED,
// ... many more specific error codes
public String formatMessage(Object... inserts);
}{ .api }
Enumeration of specific SpEL error messages with parameter formatting support.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
// Handling parse exceptions
try {
Expression exp = parser.parseExpression("invalid)syntax");
} catch (ParseException e) {
System.err.println("Parse error at position " + e.getPosition() + ": " + e.getMessage());
System.err.println("Expression: " + e.getExpressionString());
System.err.println("Detailed: " + e.toDetailedString());
}
// Handling evaluation exceptions
try {
Expression exp = parser.parseExpression("nonExistentProperty");
Object result = exp.getValue(context, new Object());
} catch (EvaluationException e) {
System.err.println("Evaluation error: " + e.getMessage());
if (e.getPosition() != -1) {
System.err.println("At position: " + e.getPosition());
}
}
// Handling method invocation exceptions
try {
Expression exp = parser.parseExpression("toString().substring(-1)"); // Invalid substring index
String result = exp.getValue(context, "test", String.class);
} catch (ExpressionInvocationTargetException e) {
System.err.println("Method invocation failed: " + e.getMessage());
System.err.println("Root cause: " + e.getCause().getClass().getSimpleName());
}{ .api }
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
try {
Expression exp = parser.parseExpression("#unknownVariable.someMethod()");
Object result = exp.getValue(context);
} catch (SpelEvaluationException e) {
SpelMessage messageCode = e.getMessageCode();
Object[] inserts = e.getInserts();
switch (messageCode) {
case PROPERTY_OR_FIELD_NOT_READABLE:
System.err.println("Property not readable: " + inserts[0]);
break;
case METHOD_NOT_FOUND:
System.err.println("Method not found: " + inserts[0] + " on type " + inserts[1]);
break;
case TYPE_CONVERSION_ERROR:
System.err.println("Cannot convert from " + inserts[0] + " to " + inserts[1]);
break;
default:
System.err.println("SpEL error: " + e.getMessage());
}
}{ .api }
public class DetailedErrorReporter {
public static void reportError(ExpressionException e) {
System.err.println("=== Expression Error Report ===");
System.err.println("Error Type: " + e.getClass().getSimpleName());
System.err.println("Message: " + e.getMessage());
if (e.getExpressionString() != null) {
System.err.println("Expression: " + e.getExpressionString());
}
if (e.getPosition() != -1) {
System.err.println("Position: " + e.getPosition());
if (e.getExpressionString() != null) {
highlightErrorPosition(e.getExpressionString(), e.getPosition());
}
}
if (e instanceof SpelEvaluationException) {
SpelEvaluationException spel = (SpelEvaluationException) e;
System.err.println("Error Code: " + spel.getMessageCode());
System.err.println("Parameters: " + Arrays.toString(spel.getInserts()));
}
System.err.println("Detailed: " + e.toDetailedString());
if (e.getCause() != null) {
System.err.println("Root Cause: " + e.getCause().getClass().getSimpleName());
System.err.println("Root Message: " + e.getCause().getMessage());
}
System.err.println("=== End Report ===");
}
private static void highlightErrorPosition(String expression, int position) {
System.err.println("Position indicator:");
System.err.println(expression);
StringBuilder pointer = new StringBuilder();
for (int i = 0; i < position; i++) {
pointer.append(' ');
}
pointer.append('^');
System.err.println(pointer.toString());
}
}
// Usage
try {
Expression exp = parser.parseExpression("obj.badProperty");
exp.getValue(context);
} catch (ExpressionException e) {
DetailedErrorReporter.reportError(e);
}{ .api }
public class SafeExpressionEvaluator {
private final ExpressionParser parser;
private final EvaluationContext context;
public SafeExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
this.parser = parser;
this.context = context;
}
public <T> Optional<T> evaluateSafely(String expression, Class<T> expectedType) {
return evaluateSafely(expression, null, expectedType);
}
public <T> Optional<T> evaluateSafely(String expression, Object rootObject, Class<T> expectedType) {
try {
Expression exp = parser.parseExpression(expression);
T result = exp.getValue(context, rootObject, expectedType);
return Optional.ofNullable(result);
} catch (ExpressionException e) {
logError(expression, e);
return Optional.empty();
}
}
public EvaluationResult evaluateWithResult(String expression, Object rootObject) {
try {
Expression exp = parser.parseExpression(expression);
Object result = exp.getValue(context, rootObject);
return EvaluationResult.success(result);
} catch (ExpressionException e) {
return EvaluationResult.failure(e);
}
}
private void logError(String expression, ExpressionException e) {
System.err.printf("Failed to evaluate expression '%s': %s%n", expression, e.getMessage());
}
public static class EvaluationResult {
private final Object value;
private final ExpressionException error;
private final boolean successful;
private EvaluationResult(Object value, ExpressionException error, boolean successful) {
this.value = value;
this.error = error;
this.successful = successful;
}
public static EvaluationResult success(Object value) {
return new EvaluationResult(value, null, true);
}
public static EvaluationResult failure(ExpressionException error) {
return new EvaluationResult(null, error, false);
}
public boolean isSuccessful() { return successful; }
public Object getValue() { return value; }
public ExpressionException getError() { return error; }
public <T> T getValueAs(Class<T> type) {
return successful ? type.cast(value) : null;
}
public Object getValueOrDefault(Object defaultValue) {
return successful ? value : defaultValue;
}
}
}
// Usage
SafeExpressionEvaluator evaluator = new SafeExpressionEvaluator(parser, context);
// Safe evaluation with Optional
Optional<String> name = evaluator.evaluateSafely("person.name", person, String.class);
if (name.isPresent()) {
System.out.println("Name: " + name.get());
} else {
System.out.println("Failed to get name");
}
// Evaluation with detailed result
EvaluationResult result = evaluator.evaluateWithResult("person.age * 2", person);
if (result.isSuccessful()) {
System.out.println("Double age: " + result.getValue());
} else {
System.err.println("Error: " + result.getError().getMessage());
}{ .api }
public class ExpressionValidator {
private final ExpressionParser parser;
public ExpressionValidator(ExpressionParser parser) {
this.parser = parser;
}
public ValidationResult validate(String expression) {
return validate(expression, null);
}
public ValidationResult validate(String expression, Class<?> expectedType) {
try {
// Check parsing
Expression exp = parser.parseExpression(expression);
// Additional validation checks
List<String> warnings = new ArrayList<>();
// Check expression length
if (expression.length() > 1000) {
warnings.add("Expression is very long (" + expression.length() + " characters)");
}
// Check for potentially dangerous patterns
if (expression.contains("T(java.lang.Runtime)")) {
return ValidationResult.invalid("Dangerous type reference detected");
}
if (expression.contains("getClass()")) {
warnings.add("Reflection access detected - may cause security issues");
}
// Check complexity (nesting depth)
int nestingDepth = calculateNestingDepth(expression);
if (nestingDepth > 10) {
warnings.add("High nesting depth (" + nestingDepth + ") may impact performance");
}
return ValidationResult.valid(warnings);
} catch (ParseException e) {
return ValidationResult.invalid(e.getMessage(), e.getPosition());
}
}
private int calculateNestingDepth(String expression) {
int depth = 0;
int maxDepth = 0;
for (char c : expression.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
depth++;
maxDepth = Math.max(maxDepth, depth);
} else if (c == ')' || c == ']' || c == '}') {
depth--;
}
}
return maxDepth;
}
public static class ValidationResult {
private final boolean valid;
private final String errorMessage;
private final int errorPosition;
private final List<String> warnings;
private ValidationResult(boolean valid, String errorMessage, int errorPosition, List<String> warnings) {
this.valid = valid;
this.errorMessage = errorMessage;
this.errorPosition = errorPosition;
this.warnings = warnings != null ? warnings : Collections.emptyList();
}
public static ValidationResult valid(List<String> warnings) {
return new ValidationResult(true, null, -1, warnings);
}
public static ValidationResult invalid(String errorMessage) {
return new ValidationResult(false, errorMessage, -1, null);
}
public static ValidationResult invalid(String errorMessage, int position) {
return new ValidationResult(false, errorMessage, position, null);
}
public boolean isValid() { return valid; }
public String getErrorMessage() { return errorMessage; }
public int getErrorPosition() { return errorPosition; }
public List<String> getWarnings() { return warnings; }
public boolean hasWarnings() { return !warnings.isEmpty(); }
}
}
// Usage
ExpressionValidator validator = new ExpressionValidator(parser);
ValidationResult result = validator.validate("person.name.toUpperCase()");
if (result.isValid()) {
System.out.println("Expression is valid");
if (result.hasWarnings()) {
System.out.println("Warnings: " + result.getWarnings());
}
} else {
System.err.println("Invalid expression: " + result.getErrorMessage());
if (result.getErrorPosition() != -1) {
System.err.println("At position: " + result.getErrorPosition());
}
}{ .api }
public class FallbackExpressionEvaluator {
private final ExpressionParser parser;
private final EvaluationContext context;
private final Map<String, Object> fallbackValues = new HashMap<>();
public FallbackExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
this.parser = parser;
this.context = context;
}
public void setFallbackValue(String expression, Object fallbackValue) {
fallbackValues.put(expression, fallbackValue);
}
public Object evaluate(String expression, Object rootObject) {
try {
Expression exp = parser.parseExpression(expression);
return exp.getValue(context, rootObject);
} catch (ExpressionException e) {
Object fallback = fallbackValues.get(expression);
if (fallback != null) {
System.out.printf("Using fallback value for expression '%s': %s%n",
expression, fallback);
return fallback;
}
// Try simplified version of the expression
Object simplifiedResult = trySimplifiedExpression(expression, rootObject, e);
if (simplifiedResult != null) {
return simplifiedResult;
}
throw e; // Re-throw if no recovery possible
}
}
private Object trySimplifiedExpression(String expression, Object rootObject, ExpressionException originalError) {
// Try removing method calls and just accessing properties
if (expression.contains(".")) {
String[] parts = expression.split("\\.");
if (parts.length > 1) {
String simpler = parts[0] + "." + parts[1]; // Take first two parts
try {
Expression exp = parser.parseExpression(simpler);
Object result = exp.getValue(context, rootObject);
System.out.printf("Fallback to simplified expression '%s' from '%s'%n",
simpler, expression);
return result;
} catch (ExpressionException e) {
// Ignore, will return null
}
}
}
return null;
}
}
// Usage
FallbackExpressionEvaluator evaluator = new FallbackExpressionEvaluator(parser, context);
evaluator.setFallbackValue("user.preferences.theme", "default");
evaluator.setFallbackValue("user.profile.avatar", "/images/default-avatar.png");
Object theme = evaluator.evaluate("user.preferences.theme", user);
// If evaluation fails, returns "default"{ .api }
public class RetryingExpressionEvaluator {
private final ExpressionParser parser;
public Object evaluateWithRetry(String expression, EvaluationContext context, Object rootObject) {
try {
Expression exp = parser.parseExpression(expression);
return exp.getValue(context, rootObject);
} catch (SpelEvaluationException e) {
// Retry with adjusted context based on error type
EvaluationContext adjustedContext = adjustContextForError(context, e);
if (adjustedContext != null) {
try {
Expression exp = parser.parseExpression(expression);
return exp.getValue(adjustedContext, rootObject);
} catch (ExpressionException retryError) {
// Both attempts failed
throw new EvaluationException("Expression failed even after context adjustment: " +
retryError.getMessage(), retryError);
}
}
throw e; // No adjustment possible
}
}
private EvaluationContext adjustContextForError(EvaluationContext context, SpelEvaluationException e) {
SpelMessage messageCode = e.getMessageCode();
if (messageCode == SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE) {
// Add more lenient property accessor
if (context instanceof StandardEvaluationContext) {
StandardEvaluationContext standardContext = (StandardEvaluationContext) context;
StandardEvaluationContext newContext = new StandardEvaluationContext(standardContext.getRootObject());
// Copy existing configuration
newContext.setPropertyAccessors(standardContext.getPropertyAccessors());
newContext.setMethodResolvers(standardContext.getMethodResolvers());
// Add lenient accessor
newContext.addPropertyAccessor(new LenientPropertyAccessor());
return newContext;
}
}
if (messageCode == SpelMessage.METHOD_NOT_FOUND) {
// Add method resolver that provides default implementations
if (context instanceof StandardEvaluationContext) {
StandardEvaluationContext standardContext = (StandardEvaluationContext) context;
StandardEvaluationContext newContext = new StandardEvaluationContext(standardContext.getRootObject());
newContext.setPropertyAccessors(standardContext.getPropertyAccessors());
newContext.setMethodResolvers(standardContext.getMethodResolvers());
newContext.addMethodResolver(new DefaultMethodResolver());
return newContext;
}
}
return null; // No adjustment available
}
}{ .api }
public class BusinessExpressionEvaluator {
private final ExpressionParser parser;
private final EvaluationContext context;
public BusinessExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
this.parser = parser;
this.context = context;
}
public Object evaluateBusinessRule(String ruleName, String expression, Object businessObject)
throws BusinessRuleException {
try {
Expression exp = parser.parseExpression(expression);
return exp.getValue(context, businessObject);
} catch (ParseException e) {
throw new BusinessRuleException(
"Invalid business rule syntax in rule '" + ruleName + "'",
e, BusinessRuleException.ErrorType.SYNTAX_ERROR
);
} catch (SpelEvaluationException e) {
SpelMessage messageCode = e.getMessageCode();
BusinessRuleException.ErrorType errorType = switch (messageCode) {
case PROPERTY_OR_FIELD_NOT_READABLE -> BusinessRuleException.ErrorType.MISSING_DATA;
case METHOD_NOT_FOUND -> BusinessRuleException.ErrorType.INVALID_OPERATION;
case TYPE_CONVERSION_ERROR -> BusinessRuleException.ErrorType.DATA_TYPE_MISMATCH;
default -> BusinessRuleException.ErrorType.EVALUATION_ERROR;
};
throw new BusinessRuleException(
"Business rule '" + ruleName + "' evaluation failed: " + e.getMessage(),
e, errorType
);
} catch (Exception e) {
throw new BusinessRuleException(
"Unexpected error in business rule '" + ruleName + "'",
e, BusinessRuleException.ErrorType.SYSTEM_ERROR
);
}
}
}
public class BusinessRuleException extends Exception {
public enum ErrorType {
SYNTAX_ERROR,
MISSING_DATA,
INVALID_OPERATION,
DATA_TYPE_MISMATCH,
EVALUATION_ERROR,
SYSTEM_ERROR
}
private final ErrorType errorType;
private final String ruleName;
public BusinessRuleException(String message, Throwable cause, ErrorType errorType) {
super(message, cause);
this.errorType = errorType;
this.ruleName = extractRuleNameFromMessage(message);
}
public ErrorType getErrorType() { return errorType; }
public String getRuleName() { return ruleName; }
private String extractRuleNameFromMessage(String message) {
// Extract rule name from error message
int start = message.indexOf("'");
int end = message.indexOf("'", start + 1);
return (start != -1 && end != -1) ? message.substring(start + 1, end) : "unknown";
}
}{ .api }
public class RobustExpressionService {
private final ExpressionParser parser;
private final ExpressionValidator validator;
private final Map<String, Expression> compiledExpressions = new ConcurrentHashMap<>();
public RobustExpressionService() {
// Use configuration that helps prevent errors
SpelParserConfiguration config = new SpelParserConfiguration(
SpelCompilerMode.IMMEDIATE, // Compile for better error detection
getClass().getClassLoader(),
true, // Auto-grow null references to prevent NPEs
true, // Auto-grow collections
100, // Reasonable auto-grow limit
10000 // Expression length limit
);
this.parser = new SpelExpressionParser(config);
this.validator = new ExpressionValidator(parser);
}
public void registerExpression(String name, String expressionString) throws InvalidExpressionException {
// Validate before storing
ValidationResult validation = validator.validate(expressionString);
if (!validation.isValid()) {
throw new InvalidExpressionException(
"Invalid expression '" + name + "': " + validation.getErrorMessage(),
validation.getErrorPosition()
);
}
// Compile and store
try {
Expression expression = parser.parseExpression(expressionString);
compiledExpressions.put(name, expression);
} catch (ParseException e) {
throw new InvalidExpressionException(
"Failed to compile expression '" + name + "': " + e.getMessage(),
e.getPosition()
);
}
}
public Object evaluate(String name, EvaluationContext context, Object rootObject)
throws ExpressionNotFoundException, EvaluationException {
Expression expression = compiledExpressions.get(name);
if (expression == null) {
throw new ExpressionNotFoundException("No expression registered with name: " + name);
}
return expression.getValue(context, rootObject);
}
}{ .api }
public class ExpressionMetrics {
private final AtomicLong parseErrorCount = new AtomicLong();
private final AtomicLong evaluationErrorCount = new AtomicLong();
private final Map<SpelMessage, AtomicLong> spelErrorCounts = new ConcurrentHashMap<>();
public void recordParseError() {
parseErrorCount.incrementAndGet();
}
public void recordEvaluationError(ExpressionException e) {
evaluationErrorCount.incrementAndGet();
if (e instanceof SpelEvaluationException) {
SpelEvaluationException spelEx = (SpelEvaluationException) e;
spelErrorCounts.computeIfAbsent(spelEx.getMessageCode(), k -> new AtomicLong())
.incrementAndGet();
}
}
public void printReport() {
System.out.println("Expression Error Metrics:");
System.out.println("Parse errors: " + parseErrorCount.get());
System.out.println("Evaluation errors: " + evaluationErrorCount.get());
if (!spelErrorCounts.isEmpty()) {
System.out.println("SpEL error breakdown:");
spelErrorCounts.entrySet().stream()
.sorted(Map.Entry.<SpelMessage, AtomicLong>comparingByValue((a, b) ->
Long.compare(b.get(), a.get())))
.forEach(entry ->
System.out.printf(" %s: %d%n", entry.getKey(), entry.getValue().get()));
}
}
}{ .api }
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring-expression