TinyRuleKit

Expressions

Expressions are optional. Static rules do not require an expression engine or MVEL on the classpath. When expressions are used, TinyRuleKit compiles them during engine creation and evaluates them with variables provided by RuleContext.

The default expression engine uses MVEL. Projects that want the default engine must add org.mvel:mvel2; projects that use only static rules or configure a custom ExpressionEngine do not need it.

When the builder explicitly enables expressions and still uses the default engine, TinyRuleKit loads that default engine during build and reuses the same loaded engine for later builders. Static rule documents do not trigger this load unless they actually contain an expr condition or action when guard.

Use expressions for small, data-oriented checks. Keep domain behavior in Java conditions and actions when the logic needs validation, services, side effects, or complex decisions.

Expression Conditions

A condition with type expr must return a boolean:

JSON
{
  "type": "expr",
  "expr": "player.level() >= 5 && wallet.coins() < 100"
}

Expression conditions are useful when the check only reads values already exposed by the context.

Action Guards With when

Actions can define a when expression. This is a subcondition for that action only, evaluated after the rule-level conditions pass:

JSON
{
  "trigger": "PLAYER_JOINED",
  "conditions": [
    {
      "type": "LEVEL_AT_LEAST",
      "params": {
        "level": 5
      }
    }
  ],
  "actions": [
    {
      "type": "SEND_MESSAGE",
      "when": "player.hasPermission('vip')",
      "params": {
        "message": "Welcome VIP"
      }
    },
    {
      "type": "GIVE_COINS",
      "when": "wallet.coins() < 100",
      "params": {
        "amount": 25
      }
    }
  ]
}

Use when to avoid creating several triggers or duplicating rules just because some actions have extra requirements. Rule-level conditions decide whether the rule matches; action guards decide which actions inside that matched rule run.

Variables

Expressions read values from RuleVariables:

JAVA
record GameContext(Player player, Wallet wallet) implements RuleContext {
    @Override
    public RuleVariables ruleVariables() {
        return RuleVariables.create()
                .put("player", player)
                .put("wallet", wallet)
                .putScalar("environment", "production");
    }
}

The variable names in JSON must match the names inserted into RuleVariables. Use put for objects that implement ExpressionAccessible; TinyRuleKit stores their safe expression view. Use putScalar explicitly for primitive wrapper values and strings. Other objects are rejected so expressions cannot inspect domain objects, services, or infrastructure APIs without a safe view.

See Expression Views before exposing domain objects.

Custom Expression Engine

Use expressionEngine to plug in another expression framework or a small project-owned expression language:

JAVA
ExpressionEngine engine = expression -> {
    RuleExpression compiled = variables -> switch (expression) {
        case "vip" -> variables.get("permission").orElse("").equals("vip");
        case "highLevel" -> ((Integer) variables.get("level").orElse(0)) >= 10;
        default -> throw new IllegalArgumentException("Unknown expression: " + expression);
    };
    return compiled;
};

RuleEngine<GameContext> rules = RuleEngine.builder(GameContext.class)
        .actions(GameRules::action)
        .expressionEngine(engine)
        .buildFromJson(json);

The engine is used only when an expression condition or action when guard is compiled. Static rule sets do not call it.

When To Use Expressions

Good candidates:

  • simple comparisons over values already exposed by the context
  • small feature flags or thresholds stored in JSON
  • action guards that should not duplicate a whole rule

Prefer Java resolver factories when:

  • logic needs services, repositories, IO, or side effects
  • the expression would become long or hard to review
  • invalid parameters should fail with a domain-specific message
  • the operation must be strongly typed and easy to test

Next Step

Continue with Expression Views to expose readable objects safely.