Spring Expression Language (SpEL) provides a powerful expression language for querying and manipulating object graphs at runtime.
—
This document covers SpEL's evaluation context implementations, providing the runtime environment for expression evaluation including variables, functions, and various resolvers.
public interface EvaluationContext {
// Root object access
TypedValue getRootObject();
// Accessor chains
List<PropertyAccessor> getPropertyAccessors();
List<IndexAccessor> getIndexAccessors();
List<ConstructorResolver> getConstructorResolvers();
List<MethodResolver> getMethodResolvers();
// Specialized resolvers
BeanResolver getBeanResolver();
TypeLocator getTypeLocator();
TypeConverter getTypeConverter();
TypeComparator getTypeComparator();
OperatorOverloader getOperatorOverloader();
// Variable management
void setVariable(String name, Object value);
Object lookupVariable(String name);
// Assignment support (6.2+)
boolean isAssignmentEnabled();
TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
}{ .api }
public class StandardEvaluationContext implements EvaluationContext {
// Constructors
public StandardEvaluationContext();
public StandardEvaluationContext(Object rootObject);
// Root object management
public void setRootObject(Object rootObject);
public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor);
public TypedValue getRootObject();
// Accessor management
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors);
public List<PropertyAccessor> getPropertyAccessors();
public void addPropertyAccessor(PropertyAccessor accessor);
public void setIndexAccessors(List<IndexAccessor> indexAccessors);
public List<IndexAccessor> getIndexAccessors();
public void addIndexAccessor(IndexAccessor accessor);
// Resolver management
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers);
public List<ConstructorResolver> getConstructorResolvers();
public void addConstructorResolver(ConstructorResolver resolver);
public void setMethodResolvers(List<MethodResolver> methodResolvers);
public List<MethodResolver> getMethodResolvers();
public void addMethodResolver(MethodResolver resolver);
// Specialized component setters
public void setBeanResolver(BeanResolver beanResolver);
public BeanResolver getBeanResolver();
public void setTypeLocator(TypeLocator typeLocator);
public TypeLocator getTypeLocator();
public void setTypeConverter(TypeConverter typeConverter);
public TypeConverter getTypeConverter();
public void setTypeComparator(TypeComparator typeComparator);
public TypeComparator getTypeComparator();
public void setOperatorOverloader(OperatorOverloader operatorOverloader);
public OperatorOverloader getOperatorOverloader();
// Variable management
public void setVariables(Map<String, Object> variables);
public void setVariable(String name, Object value);
public Object lookupVariable(String name);
// Function registration
public void registerFunction(String name, Method method);
// Assignment support
public boolean isAssignmentEnabled();
public void setAssignmentEnabled(boolean assignmentEnabled);
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
}{ .api }
// Create with default settings
StandardEvaluationContext context = new StandardEvaluationContext();
// Create with root object
Person person = new Person("John", 30);
StandardEvaluationContext context = new StandardEvaluationContext(person);
// Set root object later
context.setRootObject(person);
// Basic expression evaluation
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, String.class); // "John"{ .api }
StandardEvaluationContext context = new StandardEvaluationContext();
// Set individual variables
context.setVariable("greeting", "Hello");
context.setVariable("maxRetries", 3);
// Set multiple variables
Map<String, Object> variables = new HashMap<>();
variables.put("prefix", "Mr.");
variables.put("suffix", "Jr.");
context.setVariables(variables);
// Register functions
context.registerFunction("reverse",
StringUtils.class.getDeclaredMethod("reverse", String.class));
context.registerFunction("randomInt",
Math.class.getDeclaredMethod("random"));
// Use variables and functions in expressions
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("#greeting + ' World'");
String result = exp.getValue(context, String.class); // "Hello World"
exp = parser.parseExpression("#reverse('hello')");
String reversed = exp.getValue(context, String.class);
exp = parser.parseExpression("#randomInt() * 100");
Double random = exp.getValue(context, Double.class);{ .api }
StandardEvaluationContext context = new StandardEvaluationContext();
// Add custom property accessor
context.addPropertyAccessor(new MapAccessor());
context.addPropertyAccessor(new ReflectivePropertyAccessor());
// Add custom method resolver
context.addMethodResolver(new ReflectiveMethodResolver());
// Set type locator with custom packages
StandardTypeLocator typeLocator = new StandardTypeLocator();
typeLocator.registerImport("com.myapp.model");
typeLocator.registerImport("com.myapp.util");
context.setTypeLocator(typeLocator);
// Set custom type converter
context.setTypeConverter(new StandardTypeConverter());
// Set bean resolver (typically in Spring context)
context.setBeanResolver(new BeanFactoryResolver(applicationContext));{ .api }
public class SimpleEvaluationContext implements EvaluationContext {
// Static factory methods
public static Builder forReadOnlyDataBinding();
public static Builder forReadWriteDataBinding();
public static Builder forPropertyAccessors(PropertyAccessor... accessors);
// Builder class
public static final class Builder {
// Method access configuration
public Builder withInstanceMethods();
public Builder withStaticMethods();
// Collection access configuration
public Builder withArrayAccess();
public Builder withListAccess();
public Builder withMapAccess();
// Environment access
public Builder withEnvironmentAccess();
public Builder withSystemProperties();
public Builder withSystemEnvironment();
// Root object configuration
public Builder withTypedRootObject(Object rootObject);
public Builder withRootObject(Object rootObject);
// Type conversion
public Builder withConversionService(ConversionService conversionService);
public Builder withTypeConverter(TypeConverter typeConverter);
// Assignment control
public Builder withAssignmentDisabled();
// Build method
public SimpleEvaluationContext build();
}
}{ .api }
// Basic read-only context
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.build();
// Read-only with method access
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withInstanceMethods()
.build();
// Read-only with collection access
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withArrayAccess()
.withListAccess()
.withMapAccess()
.build();
Person person = new Person("John", 30);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, person, String.class); // "John"
exp = parser.parseExpression("name.length()"); // Requires withInstanceMethods()
Integer length = exp.getValue(context, person, Integer.class); // 4{ .api }
// Basic read-write context
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadWriteDataBinding()
.build();
// Read-write with extended features
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadWriteDataBinding()
.withInstanceMethods()
.withArrayAccess()
.withListAccess()
.withMapAccess()
.build();
Person person = new Person("John", 30);
ExpressionParser parser = new SpelExpressionParser();
// Reading
Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, person, String.class); // "John"
// Writing
exp = parser.parseExpression("name");
exp.setValue(context, person, "Jane");
// person.name is now "Jane"{ .api }
// Context with environment access
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withEnvironmentAccess()
.withSystemProperties()
.withSystemEnvironment()
.build();
ExpressionParser parser = new SpelExpressionParser();
// Access system properties
Expression exp = parser.parseExpression("systemProperties['java.version']");
String javaVersion = exp.getValue(context, String.class);
// Access environment variables
exp = parser.parseExpression("systemEnvironment['PATH']");
String path = exp.getValue(context, String.class);
// Access Spring environment (when available)
exp = parser.parseExpression("environment.getProperty('my.app.property')");
String property = exp.getValue(context, String.class);{ .api }
// Create context with specific property accessors
PropertyAccessor customAccessor = new CustomPropertyAccessor();
PropertyAccessor mapAccessor = new MapAccessor();
SimpleEvaluationContext context = SimpleEvaluationContext
.forPropertyAccessors(customAccessor, mapAccessor)
.withInstanceMethods()
.build();
// Custom accessor implementation
public class CustomPropertyAccessor implements PropertyAccessor {
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class<?>[] { MyCustomClass.class };
}
@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return target instanceof MyCustomClass && "customProperty".equals(name);
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) {
if (target instanceof MyCustomClass && "customProperty".equals(name)) {
return new TypedValue(((MyCustomClass) target).getCustomValue());
}
throw new AccessException("Cannot read property: " + name);
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) {
return false; // Read-only
}
@Override
public void write(EvaluationContext context, Object target, String name, Object newValue) {
throw new AccessException("Property is read-only: " + name);
}
}{ .api }
// Using ConversionService
ConversionService conversionService = new DefaultConversionService();
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withConversionService(conversionService)
.build();
// Using custom TypeConverter
TypeConverter customConverter = new CustomTypeConverter();
SimpleEvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withTypeConverter(customConverter)
.build();
// Custom type converter implementation
public class CustomTypeConverter implements TypeConverter {
private final ConversionService conversionService = new DefaultConversionService();
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Custom conversion logic
if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
return true;
}
return conversionService.canConvert(sourceType, targetType);
}
@Override
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
// Custom conversion implementation
if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
return new MyCustomType((String) value);
}
return conversionService.convert(value, sourceType, targetType);
}
}{ .api }
| Feature | StandardEvaluationContext | SimpleEvaluationContext |
|---|---|---|
| Performance | Slower due to reflection overhead | Faster, optimized for data binding |
| Security | Full SpEL features (potential security risks) | Restricted features (more secure) |
| Flexibility | Highly customizable | Limited but sufficient for most use cases |
| Method Invocation | All methods by default | Opt-in via builder |
| Type References | T(Class) syntax supported | Not supported |
| Constructor Calls | new Constructor() supported | Not supported |
| Bean References | @bean syntax supported | Not supported |
| Assignment | Supported by default | Opt-in via builder |
| Thread Safety | Not thread-safe | Thread-safe when immutable |
// Use SimpleEvaluationContext for:
// - Data binding scenarios
// - Security-sensitive environments
// - Performance-critical applications
// - Web applications with user input
SimpleEvaluationContext secureContext = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withInstanceMethods()
.build();
// Use StandardEvaluationContext for:
// - Full SpEL feature requirements
// - Complex expression scenarios
// - Integration with Spring frameworks
// - Development and testing environments
StandardEvaluationContext fullContext = new StandardEvaluationContext();{ .api }
// Base context with common configuration
StandardEvaluationContext baseContext = new StandardEvaluationContext();
baseContext.setVariable("appName", "MyApplication");
baseContext.setVariable("version", "1.0.0");
baseContext.registerFunction("formatDate",
DateUtils.class.getDeclaredMethod("format", Date.class, String.class));
// Specialized context inheriting from base
StandardEvaluationContext userContext = new StandardEvaluationContext();
userContext.setVariables(baseContext.lookupVariable("appName")); // Copy variables
userContext.setVariable("username", "john.doe");
userContext.setRootObject(currentUser);
// Context for specific operations
StandardEvaluationContext operationContext = new StandardEvaluationContext(userContext.getRootObject());
operationContext.setVariable("operation", "UPDATE");
operationContext.setVariable("timestamp", new Date());{ .api }
public class DynamicContextFactory {
public EvaluationContext createContext(String profile, Object rootObject) {
switch (profile.toLowerCase()) {
case "secure":
return SimpleEvaluationContext
.forReadOnlyDataBinding()
.withRootObject(rootObject)
.build();
case "standard":
return SimpleEvaluationContext
.forReadWriteDataBinding()
.withInstanceMethods()
.withArrayAccess()
.withListAccess()
.withMapAccess()
.withRootObject(rootObject)
.build();
case "full":
StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
context.addPropertyAccessor(new ReflectivePropertyAccessor());
context.addMethodResolver(new ReflectiveMethodResolver());
return context;
default:
throw new IllegalArgumentException("Unknown profile: " + profile);
}
}
}{ .api }
public class EvaluationContextPool {
private final Queue<StandardEvaluationContext> pool = new ConcurrentLinkedQueue<>();
private final int maxSize;
public EvaluationContextPool(int maxSize) {
this.maxSize = maxSize;
}
public StandardEvaluationContext borrowContext() {
StandardEvaluationContext context = pool.poll();
if (context == null) {
context = new StandardEvaluationContext();
configureContext(context);
}
return context;
}
public void returnContext(StandardEvaluationContext context) {
if (pool.size() < maxSize) {
clearContext(context);
pool.offer(context);
}
}
private void configureContext(StandardEvaluationContext context) {
// Standard configuration
context.addPropertyAccessor(new ReflectivePropertyAccessor());
context.addMethodResolver(new ReflectiveMethodResolver());
}
private void clearContext(StandardEvaluationContext context) {
// Clear variables and root object for reuse
context.setRootObject(null);
context.setVariable("temp", null); // Clear known temporary variables
}
}{ .api }
// 1. Use SimpleEvaluationContext for user-provided expressions
SimpleEvaluationContext secureContext = SimpleEvaluationContext
.forReadOnlyDataBinding()
.build(); // Minimal permissions
// 2. Validate expressions before evaluation
public boolean isSafeExpression(String expression) {
// Check for dangerous patterns
return !expression.contains("T(") && // No type references
!expression.contains("new ") && // No constructor calls
!expression.contains("@") && // No bean references
!expression.contains("#") && // No variables (if not needed)
expression.length() < 200; // Reasonable length limit
}
// 3. Use whitelisted property accessors
public class WhitelistPropertyAccessor implements PropertyAccessor {
private final Set<String> allowedProperties = Set.of("name", "id", "status");
@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return allowedProperties.contains(name) &&
target instanceof SafeDataObject;
}
// ... other methods
}{ .api }
// 1. Reuse contexts when possible (SimpleEvaluationContext)
public class ContextManager {
private final SimpleEvaluationContext sharedReadOnlyContext =
SimpleEvaluationContext.forReadOnlyDataBinding().build();
public Object evaluateReadOnly(Expression expression, Object root) {
return expression.getValue(sharedReadOnlyContext, root);
}
}
// 2. Cache context configurations
public class CachedContextFactory {
private final Map<String, EvaluationContext> contextCache = new ConcurrentHashMap<>();
public EvaluationContext getContext(String type) {
return contextCache.computeIfAbsent(type, this::createContext);
}
private EvaluationContext createContext(String type) {
// Create context based on type
return SimpleEvaluationContext.forReadOnlyDataBinding().build();
}
}
// 3. Minimize context setup overhead
public EvaluationContext createOptimizedContext(Object root) {
return SimpleEvaluationContext
.forReadOnlyDataBinding()
.withRootObject(root) // Set root directly
.build(); // No additional configuration
}{ .api }
// SimpleEvaluationContext is thread-safe when immutable
public class ThreadSafeExpressionEvaluator {
private final SimpleEvaluationContext sharedContext =
SimpleEvaluationContext.forReadOnlyDataBinding().build();
private final ExpressionParser parser = new SpelExpressionParser();
public Object evaluate(String expression, Object root) {
// Safe to share context across threads for read-only operations
Expression exp = parser.parseExpression(expression);
return exp.getValue(sharedContext, root);
}
}
// StandardEvaluationContext requires thread-local instances
public class ThreadLocalContextEvaluator {
private final ThreadLocal<StandardEvaluationContext> contextHolder =
ThreadLocal.withInitial(this::createContext);
private StandardEvaluationContext createContext() {
StandardEvaluationContext context = new StandardEvaluationContext();
// Configure context...
return context;
}
public Object evaluate(String expression, Object root) {
StandardEvaluationContext context = contextHolder.get();
context.setRootObject(root);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
return exp.getValue(context);
}
}{ .api }
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring-expression