> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gorules.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Java Rules Engine

> Integrate GoRules into your Java application.

Install the ZEN Engine and evaluate your first decision in Java.

## Installation

<CodeGroup>
  ```xml Maven theme={null}
  <dependency>
      <groupId>io.gorules</groupId>
      <artifactId>zen-engine</artifactId>
      <version>2.0.0</version>
  </dependency>
  ```

  ```groovy Gradle theme={null}
  implementation("io.gorules:zen-engine:2.0.0")
  ```
</CodeGroup>

<Note>
  The Java bindings require JDK 22 or newer. They use the FFM (Foreign Function & Memory) API to call the native Rust engine. On JDK 24+, add `--enable-native-access=ALL-UNNAMED` to your JVM options to silence native-access warnings. On versions 2.0.1 and earlier, the bundled native library must be provided manually: extract it from the jar for your platform and pass `-Duniffi.component.zen_uniffi.libraryOverride=/absolute/path/to/library`; newer versions extract it automatically.
</Note>

## Basic usage

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenDecision;
import io.gorules.zen_engine.JsonBuffer;

public class Main {
    public static void main(String[] args) throws Exception {
        var ruleJson = Main.class.getResourceAsStream("/rules/pricing.json").readAllBytes();

        try (var engine = new ZenEngine(null, null)) {
            var decision = engine.createDecision(new JsonBuffer(ruleJson));

            var input = new JsonBuffer("""
                {
                    "customer": { "tier": "gold", "yearsActive": 3 },
                    "order": { "subtotal": 150, "items": 5 }
                }
                """);

            var response = decision.evaluate(input, null).join();

            System.out.println(response.result());
            // => {"discount":0.15,"freeShipping":true}
        }
    }
}
```

## Loader

The `ZenEngine` constructor takes a `ZenLoader` that resolves decisions by key. `ZenLoader` is a sealed interface with four variants: `Callback` for loading from any storage backend, and the configurations `Static`, `Filesystem`, and `Zip`, which pre-load and pre-compile all decisions when you create the engine.

### Loader configurations

Prefer a configuration when your decisions are known up front. The engine compiles them once, so evaluations skip loading and parsing entirely.

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenLoader;
import io.gorules.zen_engine.JsonBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

// Static: decisions provided as an in-memory map
var staticEngine = new ZenEngine(new ZenLoader.Static(Map.of(
    "pricing.json", new JsonBuffer(Files.readAllBytes(Path.of("./rules/pricing.json")))
)), null);

// Filesystem: decisions resolved relative to a directory
var fsEngine = new ZenEngine(new ZenLoader.Filesystem("./rules"), null);

// Zip: decisions extracted from a zip archive
var zipEngine = new ZenEngine(new ZenLoader.Zip(Files.readAllBytes(Path.of("./decisions.zip"))), null);

var response = fsEngine.evaluate("pricing.json", new JsonBuffer("{}"), null).join();
System.out.println(response.result());
```

### Callback loader

Use `ZenLoader.Callback` to load decisions from any storage backend. The callback returns `CompletableFuture<JsonBuffer>`; complete it with `null` when the decision does not exist. Use `ConcurrentHashMap` to cache decisions for optimal performance.

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenLoader;
import io.gorules.zen_engine.JsonBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

var cache = new ConcurrentHashMap<String, byte[]>();

var engine = new ZenEngine(new ZenLoader.Callback(key -> {
    var bytes = cache.computeIfAbsent(key, k -> {
        try {
            return Files.readAllBytes(Path.of("./rules", k));
        } catch (Exception e) {
            return null;
        }
    });
    // Completing with null maps to a "decision not found" error
    return CompletableFuture.completedFuture(bytes == null ? null : new JsonBuffer(bytes));
}), null);

var response = engine.evaluate("pricing.json", new JsonBuffer("{}"), null).join();
System.out.println(response.result());
```

### AWS S3

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenLoader;
import io.gorules.zen_engine.JsonBuffer;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.regions.Region;

var s3 = S3Client.builder().region(Region.US_EAST_1).build();

// Download all decisions as a zip archive at startup
var request = GetObjectRequest.builder()
    .bucket("my-rules-bucket")
    .key("decisions.zip")
    .build();
var zipBytes = s3.getObjectAsBytes(request).asByteArray();

// ZenLoader.Zip extracts and pre-compiles every decision in the archive
var engine = new ZenEngine(new ZenLoader.Zip(zipBytes), null);

var response = engine.evaluate("pricing.json", new JsonBuffer("{}"), null).join();
System.out.println(response.result());
```

### Azure Blob Storage

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenLoader;
import io.gorules.zen_engine.JsonBuffer;
import com.azure.storage.blob.BlobServiceClientBuilder;

var blobService = new BlobServiceClientBuilder()
    .connectionString(System.getenv("AZURE_STORAGE_CONNECTION"))
    .buildClient();
var container = blobService.getBlobContainerClient("rules");

// Download all decisions as a zip archive at startup
var blob = container.getBlobClient("decisions.zip");
var zipBytes = blob.downloadContent().toBytes();

// ZenLoader.Zip extracts and pre-compiles every decision in the archive
var engine = new ZenEngine(new ZenLoader.Zip(zipBytes), null);

var response = engine.evaluate("pricing.json", new JsonBuffer("{}"), null).join();
System.out.println(response.result());
```

### Google Cloud Storage

```java theme={null}
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.ZenLoader;
import io.gorules.zen_engine.JsonBuffer;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;

var storage = StorageOptions.getDefaultInstance().getService();

// Download all decisions as a zip archive at startup
var blob = storage.get("my-rules-bucket", "decisions.zip");
var zipBytes = blob.getContent();

// ZenLoader.Zip extracts and pre-compiles every decision in the archive
var engine = new ZenEngine(new ZenLoader.Zip(zipBytes), null);

var response = engine.evaluate("pricing.json", new JsonBuffer("{}"), null).join();
System.out.println(response.result());
```

## Async evaluation

Evaluation returns `CompletableFuture` for non-blocking execution:

```java theme={null}
import io.gorules.zen_engine.ZenEngineResponse;
import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.ArrayList;

var futures = new ArrayList<CompletableFuture<ZenEngineResponse>>();

for (var input : inputs) {
    futures.add(decision.evaluate(new JsonBuffer(input), null));
}

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

for (var future : futures) {
    System.out.println(future.get().result());
}
```

## Batch evaluation

Use `evaluateBatch` to evaluate many contexts in a single call. Each request pairs a decision key with a context, and each result reports its own success or error:

```java theme={null}
import io.gorules.zen_engine.ZenBatchRequest;
import io.gorules.zen_engine.JsonBuffer;
import java.util.List;

var requests = List.of(
    new ZenBatchRequest("pricing.json", new JsonBuffer("{\"order\": {\"subtotal\": 150}}")),
    new ZenBatchRequest("pricing.json", new JsonBuffer("{\"order\": {\"subtotal\": 40}}"))
);

var results = engine.evaluateBatch(requests, null).join();

for (var result : results) {
    if (result.success()) {
        System.out.println(result.data().result());
    } else {
        System.err.println("Evaluation failed: " + result.error());
    }
}
```

## Error handling

```java theme={null}
import io.gorules.zen_engine.ZenException;

try {
    var response = decision.evaluate(input, null).join();
    System.out.println(response.result());
} catch (Exception e) {
    if (e.getCause() instanceof ZenException zenEx) {
        System.err.println("Evaluation failed: " + zenEx.getMessage());
    } else {
        throw e;
    }
}
```

## Tracing

Enable tracing to inspect decision execution:

```java theme={null}
import io.gorules.zen_engine.ZenEvaluateOptions;

var options = new ZenEvaluateOptions(null, true); // (maxDepth, trace) - pass true to enable tracing

var response = decision.evaluate(input, options).join();

System.out.println(response.trace());
// Each node's input, output, and performance timing

System.out.println(response.performance());
// Total evaluation time
```

## Expression utilities

Evaluate ZEN expressions outside of a decision context:

```java theme={null}
import io.gorules.zen_engine.ZenUniffi;
import io.gorules.zen_engine.JsonBuffer;

// Standard expressions
var context = new JsonBuffer("""
    { "a": 5, "b": 3 }
    """);
var result = ZenUniffi.evaluateExpression("a + b", context);
// => 8

var itemsContext = new JsonBuffer("""
    { "items": [1, 2, 3, 4] }
    """);
var total = ZenUniffi.evaluateExpression("sum(items)", itemsContext);
// => 10

// Unary expressions (comparison against $)
var unaryContext = new JsonBuffer("""
    { "$": 10 }
    """);
var isValid = ZenUniffi.evaluateUnaryExpression(">= 5", unaryContext);
// => true

var listContext = new JsonBuffer("""
    { "$": "US" }
    """);
var inList = ZenUniffi.evaluateUnaryExpression("'US', 'CA', 'MX'", listContext);
// => true
```

## Runtime requirements

<Note>
  The Java bindings use the FFM (Foreign Function & Memory) API from `java.lang.foreign` for interoperability with the native Rust engine. FFM requires JDK 22 or newer.
</Note>

## Best practices

**Use try-with-resources.** `ZenEngine` implements `AutoCloseable` to release native resources.

```java theme={null}
try (var engine = new ZenEngine(null, null)) {
    // use engine
}
```

**Initialize the engine once.** Create a single `ZenEngine` instance at application startup and reuse it for all evaluations.

**Implement a loader for dynamic decisions.** The loader pattern centralizes decision loading logic and enables caching with `ConcurrentHashMap`.

**Use `CompletableFuture` composition.** Chain async operations or use `allOf` for parallel evaluation of multiple decisions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.