This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data Relational 3.4.0!

Value Expressions Fundamentals

Value Expressions are a combination of Spring Expression Language (SpEL) and Property Placeholder Resolution. They combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the Environment such as configuration properties.

Expressions are expected to be defined by a trusted input such as an annotation value and not to be determined from user input.

The following code demonstrates how to use expressions in the context of annotations.

Example 1. Annotation Usage
@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
class Order {
  // …
}

Value Expressions can be defined from a sole SpEL Expression, a Property Placeholder or a composite expression mixing various expressions including literals.

Example 2. Expression Examples
#{tenantService.getOrderCollection()}                          (1)
#{(1+1) + '-hello-world'}                                      (2)
${tenant-config.suffix}                                        (3)
orders-${tenant-config.suffix}                                 (4)
#{tenantService.getOrderCollection()}-${tenant-config.suffix}  (5)
1 Value Expression using a single SpEL Expression.
2 Value Expression using a static SpEL Expression evaluating to 2-hello-world.
3 Value Expression using a single Property Placeholder.
4 Composite expression comprised of the literal orders- and the Property Placeholder ${tenant-config.suffix}.
5 Composite expression using SpEL, Property Placeholders and literals.
Using value expressions introduces a lot of flexibility to your code. Doing so requires evaluation of the expression on each usage and, therefore, value expression evaluation has an impact on the performance profile.

Parsing and Evaluation

Value Expressions are parsed by the ValueExpressionParser API. Instances of ValueExpression are thread-safe and can be cached for later use to avoid repeated parsing.

The following example shows the Value Expression API usage:

Parsing and Evaluation
  • Java

  • Kotlin

ValueParserConfiguration configuration = SpelExpressionParser::new;
ValueEvaluationContext context = ValueEvaluationContext.of(environment, evaluationContext);

ValueExpressionParser parser = ValueExpressionParser.create(configuration);
ValueExpression expression = parser.parse("Hello, World");
Object result = expression.evaluate(context);
val configuration = ValueParserConfiguration { SpelExpressionParser() }
val context = ValueEvaluationContext.of(environment, evaluationContext)

val parser = ValueExpressionParser.create(configuration)
val expression: ValueExpression = parser.parse("Hello, World")
val result: Any = expression.evaluate(context)

SpEL Expressions

SpEL Expressions follow the Template style where the expression is expected to be enclosed within the #{…} format. Expressions are evaluated using an EvaluationContext that is provided by EvaluationContextProvider. The context itself is a powerful StandardEvaluationContext allowing a wide range of operations, access to static types and context extensions.

Make sure to parse and evaluate only expressions from trusted sources such as annotations. Accepting user-provided expressions can create an entry path to exploit the application context and your system resulting in a potential security vulnerability.

Extending the Evaluation Context

EvaluationContextProvider and its reactive variant ReactiveEvaluationContextProvider provide access to an EvaluationContext. ExtensionAwareEvaluationContextProvider and its reactive variant ReactiveExtensionAwareEvaluationContextProvider are default implementations that determine context extensions from an application context, specifically ListableBeanFactory.

Extensions implement either EvaluationContextExtension or ReactiveEvaluationContextExtension to provide extension support to hydrate EvaluationContext. That are a root object, properties and functions (top-level methods).

The following example shows a context extension that provides a root object, properties, functions and an aliased function.

Implementing a EvaluationContextExtension
  • Java

  • Kotlin

@Component
public class MyExtension implements EvaluationContextExtension {

    @Override
    public String getExtensionId() {
        return "my-extension";
    }

    @Override
    public Object getRootObject() {
        return new CustomExtensionRootObject();
    }

    @Override
    public Map<String, Object> getProperties() {

        Map<String, Object> properties = new HashMap<>();

        properties.put("key", "Hello");

        return properties;
    }

    @Override
    public Map<String, Function> getFunctions() {

        Map<String, Function> functions = new HashMap<>();

        try {
            functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
            return functions;
        } catch (Exception o_O) {
            throw new RuntimeException(o_O);
        }
    }

    public static String extensionMethod() {
        return "Hello World";
    }

    public static int add(int i1, int i2) {
        return i1 + i2;
    }

}

public class CustomExtensionRootObject {

	public boolean rootObjectInstanceMethod() {
		return true;
	}

}
@Component
class MyExtension : EvaluationContextExtension {

    override fun getExtensionId(): String {
        return "my-extension"
    }

    override fun getRootObject(): Any? {
        return CustomExtensionRootObject()
    }

    override fun getProperties(): Map<String, Any> {
        val properties: MutableMap<String, Any> = HashMap()

        properties["key"] = "Hello"

        return properties
    }

    override fun getFunctions(): Map<String, Function> {
        val functions: MutableMap<String, Function> = HashMap()

        try {
            functions["aliasedMethod"] = Function(javaClass.getMethod("extensionMethod"))
            return functions
        } catch (o_O: Exception) {
            throw RuntimeException(o_O)
        }
    }

    companion object {
        fun extensionMethod(): String {
            return "Hello World"
        }

        fun add(i1: Int, i2: Int): Int {
            return i1 + i2
        }
    }
}

class CustomExtensionRootObject {
	fun rootObjectInstanceMethod(): Boolean {
		return true
	}
}

Once the above shown extension is registered, you can use its exported methods, properties and root object to evaluate SpEL expressions:

Example 3. Expression Evaluation Examples
#{add(1, 2)}                                             (1)
#{extensionMethod()}                                     (2)
#{aliasedMethod()}                                       (3)
#{key}                                                   (4)
#{rootObjectInstanceMethod()}                            (5)
1 Invoke the method add declared by MyExtension resulting in 3 as the method adds both numeric parameters and returns the sum.
2 Invoke the method extensionMethod declared by MyExtension resulting in Hello World.
3 Invoke the method aliasedMethod. The method is exposed as function and redirects into the method extensionMethod declared by MyExtension resulting in Hello World.
4 Evaluate the key property resulting in Hello.
5 Invoke the method rootObjectInstanceMethod on the root object instance CustomExtensionRootObject.

You can find real-life context extensions at SecurityEvaluationContextExtension.

Property Placeholders

Property placeholders following the form ${…} refer to properties provided typically by a PropertySource through Environment. Properties are useful to resolve against system properties, application configuration files, environment configuration or property sources contributed by secret management systems. You can find more details on the property placeholders in Spring Framework’s documentation on @Value usage.