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

advanced-features.mddocs/

Advanced Features

This document covers SpEL's advanced features including expression compilation, custom functions, variables, bean references, collection operations, and integration patterns.

Expression Compilation

Compilation Basics

SpEL can compile frequently-used expressions to Java bytecode for improved performance. Compilation is controlled through SpelParserConfiguration.

// Enable immediate compilation
SpelParserConfiguration config = new SpelParserConfiguration(
    SpelCompilerMode.IMMEDIATE,
    Thread.currentThread().getContextClassLoader()
);
SpelExpressionParser parser = new SpelExpressionParser(config);

// Parse and compile
SpelExpression expression = parser.parseRaw("name.toUpperCase() + ' (' + age + ')'");

// First evaluation compiles the expression
String result1 = expression.getValue(person, String.class);

// Subsequent evaluations use compiled bytecode (much faster)
String result2 = expression.getValue(person, String.class);

// Check compilation status
boolean isCompiled = expression.compileExpression();
System.out.println("Expression compiled: " + isCompiled);

{ .api }

Compilation Modes

// OFF: No compilation (default)
SpelParserConfiguration offConfig = new SpelParserConfiguration(
    SpelCompilerMode.OFF, null
);

// IMMEDIATE: Compile after first evaluation
SpelParserConfiguration immediateConfig = new SpelParserConfiguration(
    SpelCompilerMode.IMMEDIATE,
    Thread.currentThread().getContextClassLoader()
);

// MIXED: Adaptive compilation based on usage patterns
SpelParserConfiguration mixedConfig = new SpelParserConfiguration(
    SpelCompilerMode.MIXED,
    Thread.currentThread().getContextClassLoader()
);

{ .api }

Manual Compilation Control

SpelExpressionParser parser = new SpelExpressionParser();
SpelExpression expression = parser.parseRaw("items.![price * quantity].sum()");

// Force compilation
boolean compilationSuccessful = expression.compileExpression();
if (compilationSuccessful) {
    System.out.println("Expression successfully compiled");
} else {
    System.out.println("Expression could not be compiled - will use interpretation");
}

// Check if expression is currently compiled
if (expression.compileExpression()) {
    System.out.println("Using compiled version");
}

// Revert to interpreted mode (useful for debugging)
expression.revertToInterpreted();
System.out.println("Reverted to interpreted mode");

{ .api }

Custom Functions and Variables

Function Registration

public class MathFunctions {
    public static double sqrt(double value) {
        return Math.sqrt(value);
    }
    
    public static long factorial(int n) {
        return n <= 1 ? 1 : n * factorial(n - 1);
    }
    
    public static double distance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }
    
    public static String format(String template, Object... args) {
        return String.format(template, args);
    }
}

StandardEvaluationContext context = new StandardEvaluationContext();

// Register static methods as functions
context.registerFunction("sqrt", 
    MathFunctions.class.getDeclaredMethod("sqrt", double.class));
context.registerFunction("factorial", 
    MathFunctions.class.getDeclaredMethod("factorial", int.class));
context.registerFunction("distance", 
    MathFunctions.class.getDeclaredMethod("distance", double.class, double.class, double.class, double.class));
context.registerFunction("format", 
    MathFunctions.class.getDeclaredMethod("format", String.class, Object[].class));

ExpressionParser parser = new SpelExpressionParser();

// Use registered functions
Expression exp = parser.parseExpression("#sqrt(16)");
Double result = exp.getValue(context, Double.class); // 4.0

exp = parser.parseExpression("#factorial(5)");
Long factorial = exp.getValue(context, Long.class); // 120

exp = parser.parseExpression("#distance(0, 0, 3, 4)");
Double distance = exp.getValue(context, Double.class); // 5.0

exp = parser.parseExpression("#format('Hello %s, you are %d years old', 'John', 30)");
String formatted = exp.getValue(context, String.class); // "Hello John, you are 30 years old"

{ .api }

Variable Management

StandardEvaluationContext context = new StandardEvaluationContext();

// Set individual variables
context.setVariable("pi", Math.PI);
context.setVariable("greeting", "Hello");
context.setVariable("maxRetries", 3);

// Set multiple variables at once
Map<String, Object> variables = new HashMap<>();
variables.put("appName", "MyApplication");
variables.put("version", "1.0.0");
variables.put("debug", true);
context.setVariables(variables);

ExpressionParser parser = new SpelExpressionParser();

// Use variables in expressions
Expression exp = parser.parseExpression("2 * #pi");
Double circumference = exp.getValue(context, Double.class);

exp = parser.parseExpression("#greeting + ' World'");
String message = exp.getValue(context, String.class); // "Hello World"

exp = parser.parseExpression("#debug ? 'Debug mode' : 'Production mode'");
String mode = exp.getValue(context, String.class); // "Debug mode"

// Complex variable usage
exp = parser.parseExpression("#appName + ' v' + #version + (#debug ? ' (DEBUG)' : '')");
String fullName = exp.getValue(context, String.class); // "MyApplication v1.0.0 (DEBUG)"

{ .api }

Dynamic Variable Creation

public class DynamicVariableProvider {
    private final Map<String, Supplier<Object>> dynamicVariables = new HashMap<>();
    
    public void registerDynamicVariable(String name, Supplier<Object> provider) {
        dynamicVariables.put(name, provider);
    }
    
    public StandardEvaluationContext createContext() {
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // Set dynamic variables
        dynamicVariables.forEach((name, provider) -> {
            context.setVariable(name, provider.get());
        });
        
        return context;
    }
}

DynamicVariableProvider provider = new DynamicVariableProvider();
provider.registerDynamicVariable("currentTime", System::currentTimeMillis);
provider.registerDynamicVariable("randomId", () -> UUID.randomUUID().toString());
provider.registerDynamicVariable("freeMemory", () -> Runtime.getRuntime().freeMemory());

// Each time we create a context, variables have fresh values
EvaluationContext context1 = provider.createContext();
EvaluationContext context2 = provider.createContext(); // Different random values

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'ID: ' + #randomId + ', Time: ' + #currentTime");

String result1 = exp.getValue(context1, String.class);
String result2 = exp.getValue(context2, String.class);
// Results will have different random IDs and timestamps

{ .api }

Bean References (Spring Integration)

Bean Resolver Configuration

// In a Spring application context
@Configuration
public class SpelConfiguration {
    
    @Bean
    public StandardEvaluationContext evaluationContext(ApplicationContext applicationContext) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // Enable bean references (@beanName syntax)
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        
        return context;
    }
}

// Service beans
@Service
public class UserService {
    public User findById(Long id) {
        // Implementation...
        return new User(id, "John Doe");
    }
    
    public List<User> findAll() {
        // Implementation...
        return Arrays.asList(
            new User(1L, "John Doe"),
            new User(2L, "Jane Smith")
        );
    }
}

@Service
public class EmailService {
    public void sendEmail(String to, String subject, String body) {
        System.out.printf("Sending email to %s: %s%n", to, subject);
    }
}

{ .api }

Using Bean References in Expressions

// Assuming Spring context with beans
ExpressionParser parser = new SpelExpressionParser();

// Reference beans using @ syntax
Expression exp = parser.parseExpression("@userService.findById(1)");
User user = exp.getValue(context, User.class);

exp = parser.parseExpression("@userService.findAll().size()");
Integer userCount = exp.getValue(context, Integer.class);

// Complex bean method chaining
exp = parser.parseExpression("@userService.findById(1).getName().toUpperCase()");
String upperName = exp.getValue(context, String.class);

// Bean references in conditional expressions
exp = parser.parseExpression("@userService.findById(1) != null ? 'User found' : 'User not found'");
String message = exp.getValue(context, String.class);

// Using beans with variables
context.setVariable("userId", 42L);
exp = parser.parseExpression("@userService.findById(#userId)");
User userById = exp.getValue(context, User.class);

{ .api }

Factory Bean Access

// Access factory beans using &beanName syntax
Expression exp = parser.parseExpression("&myFactoryBean");
FactoryBean<?> factory = exp.getValue(context, FactoryBean.class);

// Get the actual object created by the factory
exp = parser.parseExpression("@myFactoryBean");
Object factoryProduct = exp.getValue(context);

{ .api }

Collection Operations

Collection Selection (Filtering)

public class Product {
    private String name;
    private double price;
    private String category;
    private boolean inStock;
    
    // constructors, getters, setters...
}

List<Product> products = Arrays.asList(
    new Product("Laptop", 999.99, "Electronics", true),
    new Product("Mouse", 29.99, "Electronics", false),
    new Product("Book", 19.99, "Books", true),
    new Product("Keyboard", 79.99, "Electronics", true)
);

StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("products", products);

ExpressionParser parser = new SpelExpressionParser();

// Selection: filter collection using .?[condition]
Expression exp = parser.parseExpression("#products.?[inStock == true]");
List<Product> inStockProducts = (List<Product>) exp.getValue(context);

// Filter by price range
exp = parser.parseExpression("#products.?[price >= 50 and price <= 100]");
List<Product> midRangeProducts = (List<Product>) exp.getValue(context);

// Filter by category
exp = parser.parseExpression("#products.?[category == 'Electronics']");
List<Product> electronics = (List<Product>) exp.getValue(context);

// Complex filtering
exp = parser.parseExpression("#products.?[category == 'Electronics' and inStock and price > 50]");
List<Product> availableElectronics = (List<Product>) exp.getValue(context);

// First match: .^[condition]
exp = parser.parseExpression("#products.^[price > 100]");
Product firstExpensive = (Product) exp.getValue(context);

// Last match: .$[condition]
exp = parser.parseExpression("#products.$[inStock == true]");
Product lastInStock = (Product) exp.getValue(context);

{ .api }

Collection Projection (Transformation)

// Projection: transform collection using .![expression]
Expression exp = parser.parseExpression("#products.![name]");
List<String> productNames = (List<String>) exp.getValue(context);
// ["Laptop", "Mouse", "Book", "Keyboard"]

exp = parser.parseExpression("#products.![name + ' - $' + price]");
List<String> nameAndPrice = (List<String>) exp.getValue(context);
// ["Laptop - $999.99", "Mouse - $29.99", ...]

// Complex projections
exp = parser.parseExpression("#products.![name.toUpperCase() + (inStock ? ' (Available)' : ' (Out of Stock)')]");
List<String> statusList = (List<String>) exp.getValue(context);

// Nested object projection
public class Order {
    private List<Product> items;
    // constructor, getters, setters...
}

List<Order> orders = Arrays.asList(
    new Order(Arrays.asList(products.get(0), products.get(1))),
    new Order(Arrays.asList(products.get(2)))
);
context.setVariable("orders", orders);

// Project nested collections
exp = parser.parseExpression("#orders.![items.![name]]");
List<List<String>> nestedNames = (List<List<String>>) exp.getValue(context);

// Flatten nested projections
exp = parser.parseExpression("#orders.![items].flatten()");
List<Product> allOrderItems = (List<Product>) exp.getValue(context);

{ .api }

Map Operations

Map<String, Integer> inventory = new HashMap<>();
inventory.put("laptop", 10);
inventory.put("mouse", 50);
inventory.put("keyboard", 25);

context.setVariable("inventory", inventory);

// Map key selection
Expression exp = parser.parseExpression("#inventory.?[value > 20]");
Map<String, Integer> highInventory = (Map<String, Integer>) exp.getValue(context);
// {mouse=50, keyboard=25}

// Map projection (values)
exp = parser.parseExpression("#inventory.![value]");
List<Integer> inventoryCounts = (List<Integer>) exp.getValue(context);
// [10, 50, 25]

// Map projection (keys)  
exp = parser.parseExpression("#inventory.![key]");
List<String> inventoryItems = (List<String>) exp.getValue(context);
// ["laptop", "mouse", "keyboard"]

// Complex map operations
exp = parser.parseExpression("#inventory.?[value < 30].![key + ': ' + value]");
List<String> lowInventoryReport = (List<String>) exp.getValue(context);
// ["laptop: 10", "keyboard: 25"]

{ .api }

Aggregation Operations

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
context.setVariable("numbers", numbers);

// Custom aggregation functions
context.registerFunction("sum", 
    Arrays.class.getDeclaredMethod("stream", Object[].class));

// Sum using projection and custom function
Expression exp = parser.parseExpression("#products.![price].stream().mapToDouble(p -> p).sum()");
// Note: This requires Java 8 streams support in the context

// Alternative: Calculate sum manually
exp = parser.parseExpression("#products.![price]");
List<Double> prices = (List<Double>) exp.getValue(context);
double totalPrice = prices.stream().mapToDouble(Double::doubleValue).sum();

// Count operations
exp = parser.parseExpression("#products.?[inStock == true].size()");
Integer inStockCount = exp.getValue(context, Integer.class);

// Min/Max operations (requires custom functions or preprocessing)
public class CollectionFunctions {
    public static Double max(List<Double> values) {
        return values.stream().mapToDouble(Double::doubleValue).max().orElse(0.0);
    }
    
    public static Double min(List<Double> values) {
        return values.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
    }
    
    public static Double avg(List<Double> values) {
        return values.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
    }
}

context.registerFunction("max", 
    CollectionFunctions.class.getDeclaredMethod("max", List.class));
context.registerFunction("min", 
    CollectionFunctions.class.getDeclaredMethod("min", List.class));
context.registerFunction("avg", 
    CollectionFunctions.class.getDeclaredMethod("avg", List.class));

// Use aggregation functions
exp = parser.parseExpression("#max(#products.![price])");
Double maxPrice = exp.getValue(context, Double.class);

exp = parser.parseExpression("#avg(#products.![price])");
Double avgPrice = exp.getValue(context, Double.class);

{ .api }

Advanced Operators and Expressions

Safe Navigation Operator

public class Address {
    private String city;
    private String country;
    // constructors, getters, setters...
}

public class Person {
    private String name;
    private Address address; // might be null
    // constructors, getters, setters...
}

Person person = new Person("John");
// address is null

ExpressionParser parser = new SpelExpressionParser();

// Safe navigation prevents NullPointerException
Expression exp = parser.parseExpression("address?.city");
String city = exp.getValue(person, String.class); // null, no exception

// Without safe navigation would throw NPE:
// exp = parser.parseExpression("address.city"); // NullPointerException

// Chained safe navigation
exp = parser.parseExpression("address?.city?.toUpperCase()");
String upperCity = exp.getValue(person, String.class); // null

// Safe navigation with method calls
exp = parser.parseExpression("address?.toString()?.length()");
Integer addressLength = exp.getValue(person, Integer.class); // null

{ .api }

Elvis Operator

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

// Elvis operator provides default values for null
context.setVariable("name", null);
Expression exp = parser.parseExpression("#name ?: 'Unknown'");
String name = exp.getValue(context, String.class); // "Unknown"

context.setVariable("name", "John");
name = exp.getValue(context, String.class); // "John"

// Elvis with method calls
Person person = new Person(null); // name is null
exp = parser.parseExpression("name?.toUpperCase() ?: 'NO NAME'");
String upperName = exp.getValue(person, String.class); // "NO NAME"

// Complex elvis expressions
exp = parser.parseExpression("address?.city ?: address?.country ?: 'Unknown Location'");
String location = exp.getValue(person, String.class); // "Unknown Location"

// Elvis with collection operations
context.setVariable("items", null);
exp = parser.parseExpression("#items ?: {}"); // Empty map as default
Map<String, Object> items = (Map<String, Object>) exp.getValue(context);

context.setVariable("list", null);
exp = parser.parseExpression("#list ?: {1, 2, 3}"); // Default list
List<Integer> list = (List<Integer>) exp.getValue(context);

{ .api }

Regular Expression Matching

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

// Basic pattern matching
context.setVariable("email", "user@example.com");
Expression exp = parser.parseExpression("#email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'");
Boolean isValidEmail = exp.getValue(context, Boolean.class); // true

// Case-insensitive matching
context.setVariable("text", "Hello World");
exp = parser.parseExpression("#text matches '(?i)hello.*'");
Boolean matches = exp.getValue(context, Boolean.class); // true

// Pattern matching in filtering
List<String> names = Arrays.asList("John", "Jane", "Bob", "Alice", "Andrew");
context.setVariable("names", names);

exp = parser.parseExpression("#names.?[#this matches 'A.*']"); // Names starting with 'A'
List<String> aNames = (List<String>) exp.getValue(context); // ["Alice", "Andrew"]

// Complex pattern matching
context.setVariable("phoneNumber", "+1-555-123-4567");
exp = parser.parseExpression("#phoneNumber matches '\\+1-\\d{3}-\\d{3}-\\d{4}'");
Boolean isValidPhone = exp.getValue(context, Boolean.class); // true

// URL validation
context.setVariable("url", "https://www.example.com/path?param=value");
exp = parser.parseExpression("#url matches 'https?://[\\w.-]+(:[0-9]+)?(/.*)?'");
Boolean isValidUrl = exp.getValue(context, Boolean.class); // true

{ .api }

Expression Templates and Composition

Template Expressions

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

Map<String, Object> user = new HashMap<>();
user.put("firstName", "John");
user.put("lastName", "Doe");
user.put("age", 30);
user.put("city", "New York");

context.setVariables(user);

// Template with multiple expressions
String template = "Hello #{#firstName} #{#lastName}! You are #{#age} years old and live in #{#city}.";
Expression exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
String message = exp.getValue(context, String.class);
// "Hello John Doe! You are 30 years old and live in New York."

// Conditional templates
template = "Welcome #{#firstName}#{#age >= 18 ? ' (Adult)' : ' (Minor)'}!";
exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
String welcome = exp.getValue(context, String.class);
// "Welcome John (Adult)!"

// Mathematical expressions in templates
template = "Next year you will be #{#age + 1} years old.";
exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
String nextYear = exp.getValue(context, String.class);
// "Next year you will be 31 years old."

{ .api }

Custom Template Contexts

// Custom template delimiters
TemplateParserContext customContext = new TemplateParserContext("${", "}");

String template = "Hello ${#name}, today is ${T(java.time.LocalDate).now()}";
Expression exp = parser.parseExpression(template, customContext);

context.setVariable("name", "World");
String result = exp.getValue(context, String.class);
// "Hello World, today is 2023-12-07"

// XML-friendly delimiters
TemplateParserContext xmlContext = new TemplateParserContext("{{", "}}");

template = "<greeting>Hello {{#name}}</greeting>";
exp = parser.parseExpression(template, xmlContext);
result = exp.getValue(context, String.class);
// "<greeting>Hello World</greeting>"

{ .api }

Expression Composition

public class ExpressionComposer {
    private final ExpressionParser parser = new SpelExpressionParser();
    private final Map<String, Expression> namedExpressions = new HashMap<>();
    
    public void defineExpression(String name, String expressionString) {
        Expression expr = parser.parseExpression(expressionString);
        namedExpressions.put(name, expr);
    }
    
    public Expression composeExpression(String... expressionNames) {
        // Create composite expression that evaluates multiple sub-expressions
        StringBuilder composite = new StringBuilder();
        
        for (int i = 0; i < expressionNames.length; i++) {
            if (i > 0) {
                composite.append(" + ");
            }
            composite.append("(").append(getExpressionString(expressionNames[i])).append(")");
        }
        
        return parser.parseExpression(composite.toString());
    }
    
    private String getExpressionString(String name) {
        Expression expr = namedExpressions.get(name);
        return expr != null ? expr.getExpressionString() : "''";
    }
    
    public Object evaluateComposite(EvaluationContext context, String... expressionNames) {
        Expression composite = composeExpression(expressionNames);
        return composite.getValue(context);
    }
}

// Usage
ExpressionComposer composer = new ExpressionComposer();
composer.defineExpression("greeting", "'Hello '");
composer.defineExpression("name", "#user.name");
composer.defineExpression("suffix", "', welcome!'");

Expression greeting = composer.composeExpression("greeting", "name", "suffix");

context.setVariable("user", new User("Alice"));
String result = greeting.getValue(context, String.class);
// "Hello Alice, welcome!"

{ .api }

Performance Optimization Strategies

Expression Caching and Pooling

public class OptimizedExpressionService {
    private final ExpressionParser parser;
    private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
    private final Map<String, AtomicLong> usageStats = new ConcurrentHashMap<>();
    
    public OptimizedExpressionService() {
        // Configure for performance
        SpelParserConfiguration config = new SpelParserConfiguration(
            SpelCompilerMode.IMMEDIATE,
            Thread.currentThread().getContextClassLoader(),
            false, // Disable auto-grow for predictability
            false,
            0,
            10000
        );
        this.parser = new SpelExpressionParser(config);
    }
    
    public Expression getExpression(String expressionString) {
        return expressionCache.computeIfAbsent(expressionString, expr -> {
            System.out.println("Compiling new expression: " + expr);
            return parser.parseExpression(expr);
        });
    }
    
    public Object evaluate(String expressionString, EvaluationContext context, Object rootObject) {
        Expression expr = getExpression(expressionString);
        
        // Track usage
        usageStats.computeIfAbsent(expressionString, k -> new AtomicLong()).incrementAndGet();
        
        return expr.getValue(context, rootObject);
    }
    
    public void printUsageStats() {
        System.out.println("Expression Usage Statistics:");
        usageStats.entrySet().stream()
            .sorted(Map.Entry.<String, AtomicLong>comparingByValue(
                (a, b) -> Long.compare(b.get(), a.get())))
            .limit(10)
            .forEach(entry -> 
                System.out.printf("  %dx: %s%n", entry.getValue().get(), entry.getKey()));
    }
    
    // Precompile frequently used expressions
    public void warmUp(String... expressions) {
        for (String expr : expressions) {
            Expression compiled = getExpression(expr);
            if (compiled instanceof SpelExpression) {
                ((SpelExpression) compiled).compileExpression();
            }
        }
    }
}

// Usage
OptimizedExpressionService service = new OptimizedExpressionService();

// Warm up with known frequent expressions
service.warmUp(
    "user.name.toUpperCase()",
    "order.items.![price * quantity].sum()",
    "product.inStock and product.price > 0"
);

// Use service
Object result = service.evaluate("user.name.toUpperCase()", context, user);

{ .api }

Specialized Context Configuration

public class PerformanceEvaluationContext extends StandardEvaluationContext {
    
    public PerformanceEvaluationContext() {
        super();
        configureForPerformance();
    }
    
    public PerformanceEvaluationContext(Object rootObject) {
        super(rootObject);
        configureForPerformance();
    }
    
    private void configureForPerformance() {
        // Use optimized accessors in order of likely usage
        setPropertyAccessors(Arrays.asList(
            new DataBindingPropertyAccessor(),    // Fastest for simple properties
            new ReflectivePropertyAccessor(false) // Read-only for security & speed
        ));
        
        // Minimal method resolution
        setMethodResolvers(Arrays.asList(
            new DataBindingMethodResolver()
        ));
        
        // Optimized type handling
        setTypeConverter(new StandardTypeConverter());
        setTypeComparator(new StandardTypeComparator());
        setOperatorOverloader(new StandardOperatorOverloader());
        
        // Fast type locator with common imports
        StandardTypeLocator typeLocator = new StandardTypeLocator();
        typeLocator.registerImport("", "java.lang");
        typeLocator.registerImport("", "java.util");
        typeLocator.registerImport("", "java.time");
        setTypeLocator(typeLocator);
    }
    
    // Bulk variable setting for reduced overhead
    public void setVariablesBulk(Map<String, Object> variables) {
        variables.forEach(this::setVariable);
    }
}

{ .api }

Integration Patterns

Spring Boot Integration

@Configuration
@EnableConfigurationProperties(SpelProperties.class)
public class SpelAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public SpelExpressionParser spelExpressionParser(SpelProperties properties) {
        SpelParserConfiguration config = new SpelParserConfiguration(
            properties.getCompilerMode(),
            Thread.currentThread().getContextClassLoader(),
            properties.isAutoGrowNullReferences(),
            properties.isAutoGrowCollections(),
            properties.getMaximumAutoGrowSize(),
            properties.getMaximumExpressionLength()
        );
        return new SpelExpressionParser(config);
    }
    
    @Bean
    @ConditionalOnMissingBean
    public StandardEvaluationContext standardEvaluationContext(
            ApplicationContext applicationContext,
            SpelProperties properties) {
        
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        if (properties.isBeanReferencesEnabled()) {
            context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        }
        
        return context;
    }
}

@ConfigurationProperties(prefix = "spel")
public class SpelProperties {
    private SpelCompilerMode compilerMode = SpelCompilerMode.OFF;
    private boolean autoGrowNullReferences = false;
    private boolean autoGrowCollections = false;
    private int maximumAutoGrowSize = Integer.MAX_VALUE;
    private int maximumExpressionLength = 10000;
    private boolean beanReferencesEnabled = true;
    
    // getters and setters...
}

{ .api }

Expression-Based Validation

@Component
public class ExpressionValidator {
    private final SpelExpressionParser parser = new SpelExpressionParser();
    
    public boolean validate(Object object, String validationExpression) {
        try {
            Expression expr = parser.parseExpression(validationExpression);
            Boolean result = expr.getValue(object, Boolean.class);
            return result != null && result;
        } catch (Exception e) {
            return false;
        }
    }
    
    // Annotation-based validation
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = ExpressionConstraintValidator.class)
    public @interface ValidExpression {
        String value();
        String message() default "Expression validation failed";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    }
    
    public static class ExpressionConstraintValidator 
            implements ConstraintValidator<ValidExpression, Object> {
        
        private String expression;
        private final ExpressionValidator validator = new ExpressionValidator();
        
        @Override
        public void initialize(ValidExpression constraint) {
            this.expression = constraint.value();
        }
        
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {
            return value == null || validator.validate(value, expression);
        }
    }
}

// Usage
@ValidExpression("age >= 18 and age <= 120")
public class Person {
    private int age;
    private String name;
    // getters and setters...
}

{ .api }

Best Practices for Advanced Usage

Security Considerations

// Secure expression evaluation service
public class SecureExpressionService {
    private final SpelExpressionParser parser;
    private final Set<String> allowedExpressionPatterns;
    
    public SecureExpressionService() {
        // Disable compilation for security
        SpelParserConfiguration config = new SpelParserConfiguration(
            SpelCompilerMode.OFF, null
        );
        this.parser = new SpelExpressionParser(config);
        
        // Define allowed expression patterns
        this.allowedExpressionPatterns = Set.of(
            "^[a-zA-Z_][a-zA-Z0-9_.]*$",  // Simple property access
            "^#[a-zA-Z_][a-zA-Z0-9_]*$",  // Variable access
            "^[^T(]*$"                     // No type references
        );
    }
    
    public Object safeEvaluate(String expression, EvaluationContext context, Object root) {
        // Validate expression against security rules
        if (!isExpressionSafe(expression)) {
            throw new SecurityException("Expression not allowed: " + expression);
        }
        
        // Use restricted context
        SimpleEvaluationContext secureContext = SimpleEvaluationContext
            .forReadOnlyDataBinding()
            .build();
            
        Expression expr = parser.parseExpression(expression);
        return expr.getValue(secureContext, root);
    }
    
    private boolean isExpressionSafe(String expression) {
        return allowedExpressionPatterns.stream()
            .anyMatch(pattern -> expression.matches(pattern)) &&
            !containsDangerousPatterns(expression);
    }
    
    private boolean containsDangerousPatterns(String expression) {
        String[] dangerousPatterns = {
            "T(", "new ", "getClass", "forName", "invoke"
        };
        
        return Arrays.stream(dangerousPatterns)
            .anyMatch(expression::contains);
    }
}

{ .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