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., ArrayList for List, Integer for Number)

  • 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:

  1. DynamicVariableResolver.resolve(variable) is called

  2. The resolver checks for an entity-scoped value first (if a processing context exists)

  3. Falls back to the global registry

  4. Returns the default value if no override is found

  5. Converts the value to the appropriate KernelF type