21 — Advanced
Async Execution
RuleSets and RuleFlows can be executed asynchronously using CompletableFuture — both implement the AsyncRunnable interface (new in 2.0.0). This enables non-blocking rule evaluation and parallel execution, leveraging the ExecutorService configured in the RuleContext.
Preferred: Async Inside a RuleFlow
The recommended way to run rules asynchronously is the RuleFlow async pipeline — asyncRun(...) launches steps in parallel and await / awaitAll / awaitAny coordinate them, with binding isolation and exception handling built in:
RuleFlow<RuleContext> flow = RuleFlow.builder()
.name("ParallelChecks")
.asyncRun(pricingRuleSet, spec -> spec
.as("pricingFuture")
.withImmutableBindings()) // isolated snapshot — no races
.asyncRun(inventoryRuleSet, spec -> spec
.as("inventoryFuture")
.withImmutableBindings())
.awaitAll("pricingFuture", "inventoryFuture")
.build();
flow.run(order -> myOrder);
Default Executor
By default, rulii uses a fixed thread pool sized to the number of available processors. This provides sensible concurrency without requiring explicit configuration.
Direct Async Execution (Low-Level)
For one-off cases, runAsync(...) is available directly on a RuleSet or RuleFlow:
RuleSet<?> ruleSet = RuleSet.builder()
.with("AsyncRules")
.rule(/* ... rules ... */)
.build();
Bindings bindings = Bindings.builder().standard();
bindings.bind("name", "Alice");
// Run asynchronously
CompletableFuture<?> future = ruleSet.runAsync(bindings);
// Wait for result
future.join();
// Or use callbacks
future.thenAccept(result -> {
System.out.println("Rules completed!");
});
Custom ExecutorService
You can provide your own ExecutorService to control the thread pool size and behavior:
ExecutorService executor = Executors.newFixedThreadPool(4);
RuleContext ctx = RuleContext.builder()
.executorService(executor)
.build(bindings);
CompletableFuture<?> future = ruleSet.runAsync(ctx);
Use Cases
Parallel Rule Sets
Run independent rule sets concurrently for faster evaluation
Non-Blocking Validation
Validate inputs without blocking the calling thread
Thread Safety Warning
Caution: Shared Mutable Bindings
Never reuse the same RuleContext for concurrent runAsync invocations — execution mutates the context's binding scope stack, and concurrent executions can corrupt or discard each other's scopes. Use a separate RuleContext per concurrent run, or better, orchestrate the parallel work inside a RuleFlow with asyncRun(...).withImmutableBindings(), which isolates each task automatically.