CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework--spring-expression

Spring Expression Language (SpEL) provides a powerful expression language for querying and manipulating object graphs at runtime.

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

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

Exception Hierarchy

ExpressionException (Base Class)

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.

Core Exception Types

ParseException

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.

EvaluationException

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.

ExpressionInvocationTargetException

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.

AccessException

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.

SpEL-Specific Exceptions

SpelEvaluationException

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.

SpelParseException

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.

SpelMessage Enum

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.

Error Handling Examples

Basic Exception Handling

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 }

SpEL-Specific Exception Handling

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 }

Detailed Error Reporting

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 }

Robust Expression Evaluation

Safe Expression Evaluator

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 }

Expression Validation

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 }

Error Recovery Strategies

Fallback Expression Evaluator

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 }

Retry with Context Adjustment

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 }

Custom Exception Handling

Domain-Specific Exception Wrapper

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 }

Best Practices

Exception Handling Guidelines

  1. Catch Specific Exceptions: Catch the most specific exception types first
  2. Preserve Context: Include expression string and position in error messages
  3. Log Appropriately: Use different log levels for different exception types
  4. Provide Fallbacks: Implement graceful degradation when possible
  5. Validate Early: Validate expressions at configuration time, not runtime
  6. Use Structured Errors: Leverage SpelMessage enum for consistent error handling

Error Prevention Strategies

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 }

Monitoring and Metrics

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

docs

advanced-features.md

error-handling.md

evaluation-contexts.md

expression-evaluation.md

index.md

method-constructor-resolution.md

property-index-access.md

spel-configuration.md

type-system-support.md

tile.json