Dynamic Variables
A dynamic variable is a type-safe definition of a configurable value that can be overridden at runtime. Dynamic variables are defined using the Rules Framework DynamicValueLibrary concept and generate to Java code that integrates with the DynamicExpressionRegistry.
Overview
Dynamic variables provide a way to externalize configuration from your business rules. Each dynamic variable has:
-
A model name - identifies which MPS model the variable belongs to
-
A variable name - the unique name within the model
-
A type - the Java class representing the value type (primitives or lists)
-
A default value - used when no configuration override is provided
When your MPS model is compiled, dynamic variables generate a DynamicVariable<T> record that encapsulates this metadata.
Type Safety
Dynamic variables are strongly typed. The supported types are:
-
Primitive types:
Number,String,Boolean -
List types:
List<Number>,List<String>,List<Boolean>
This type safety is enforced both at compile time (in the generated code) and at runtime (when loading configuration values).
Runtime Type Validation
When configuration sources are loaded at application startup, the ConfigurableValuesManager validates that each configuration value’s type matches the corresponding DynamicVariable type definition. This validation:
-
Occurs at startup - Configuration errors are detected immediately when the application loads
-
Validates on push updates - Dynamic sources that support runtime updates also trigger validation
-
Uses type assignability - Subtypes are accepted (e.g.,
ArrayListforList,IntegerforNumber) -
Allows null values - Null is considered valid and bypasses type checking
If a type mismatch is detected, the application fails fast with a detailed error message:
java.lang.IllegalArgumentException: Type mismatch for configuration variable with key:
'paymentService_upperBound' - expected type java.lang.Integer but got java.lang.String
This ensures configuration errors are caught early rather than causing runtime failures in business logic.
Key Format
Each dynamic variable has a composite key used for registry lookups:
{modelName}_{variableName}
For example, a variable named upperBound in a model called paymentService would have the key:
paymentService_upperBound
This key format is used when configuring values in HOCON files or registering them programmatically.
Generated Code
When you define a dynamic variable in MPS, the code generator creates a DynamicVariablesLibrary class:
public class DynamicVariablesLib implements DynamicVariableLibrary {
// Static variable definitions with defaults
public static final DynamicVariable<Number> upperBound =
new DynamicVariable<>(
"paymentService", // model name
"upperBound", // variable name
Number.class, // type
BigInteger.valueOf(1000) // default value
);
public static final DynamicVariable<List<String>> allowedCurrencies =
DynamicVariable.ofList(
"paymentService",
"allowedCurrencies",
String.class,
Arrays.asList("USD", "EUR", "GBP")
);
// Singleton instance
public static DynamicVariableLibrary INSTANCE = new DynamicVariablesLibrary();
// Instance field containing all variables
private final List<DynamicVariable<?>> dynamicVariables;
// Constructor initializes the list
private DynamicVariablesLib() {
dynamicVariables = new ArrayList<>();
dynamicVariables.add(upperBound);
dynamicVariables.add(allowedCurrencies);
}
@Override
public List<DynamicVariable<?>> getDynamicVariables() {
return dynamicVariables;
}
}
The generated code also references DynamicVariableResolver.resolve() to retrieve the current value at runtime.
DynamicVariable Record
The DynamicVariable<T> record is defined as:
public record DynamicVariable<T>(
String modelName,
String name,
Class<T> rawType,
Class<?> elementType,
T defaultValue
) {
// Key generation
public String key() {
return modelName + "_" + name;
}
// Factory for list types
public static <E> DynamicVariable<List<E>> ofList(
String modelName,
String name,
Class<E> elementType,
List<E> defaultValue
) { ... }
}
Resolution Flow
When generated code needs a dynamic variable value:
-
DynamicVariableResolver.resolve(variable)is called -
The resolver checks for an entity-scoped value first (if a processing context exists)
-
Falls back to the global registry
-
Returns the default value if no override is found
-
Converts the value to the appropriate KernelF type