v2.0

GoRules Version 2 is here - redesigned, now with managed cloud.GoRules Version 2 is here!

Watch the launch videoWatch

Switch from Camunda to GoRules.

A step-by-step guide to moving your DMN models to GoRules: export the .dmn files, convert FEEL and JUEL expressions with the AI copilot, validate in parallel, and cut over.

What you get with GoRules

Where your rules land: the parts of GoRules most relevant to this migration.

One expression language everywhere

Decision table cells, expression nodes, and switch conditions all use Zen - a single expression language with 60+ built-in functions (sum, avg, filter, map, date, duration, matches). For logic beyond expressions, functionNode runs sandboxed JavaScript with zod, big.js, dayjs, and http imports.

AI copilot for authoring and conversion

The copilot converts uploaded rule files into decision graphs, edits tables from plain-language instructions, generates test cases, and explains what a decision did on a given input. Deploys and releases stay explicit human actions.

Stateless Rust engine with per-node tracing

The Zen Engine evaluates a decision per request: JSON in, DAG traversal in topological order, JSON out. Each node runs exactly once, and the response can include a per-node trace with input, output, and sub-millisecond timing.

Native SDKs across languages

The same Rust core ships as @gorules/zen-engine for Node.js, zen-engine for Python and Rust, zen-go for Go, io.gorules:zen-engine for JVM (Java and Kotlin), GoRules.ZenEngine for .NET, plus Swift, Android, and browser or Lambda via WASM.

The migration path

A step-by-step route from your current setup to running your rules on Zen Engine.

1. Export DMN XML and audit expression languages

[Camunda 8] Export your .dmn resources from Web Modeler or pull them from your deployment artifacts - expressions in these files are FEEL, and translation to Zen is largely direct. [Camunda 7] Export each .dmn file and audit every cell for an expressionLanguage attribute. Cells with expressionLanguage="juel", expressionLanguage="groovy", expressionLanguage="javascript", or any other JSR-223 language need separate handling (see step 4). If your engine configuration sets dmnFeelEnableLegacyBehavior or calls setEnableFeelLegacyBehavior(true), older files in that project may use JUEL in cells without an explicit attribute - verify each one.

2. Map DMN decision tables to decisionTableNode

Each <decisionTable> maps to one decisionTableNode. Input clauses become input columns (the inputExpression text becomes the column field); output clauses become output columns (name becomes the output field). DMN UNIQUE/ANY/FIRST hit policies map to hitPolicy: "first"; RULE ORDER and COLLECT map to hitPolicy: "collect". COLLECT with an aggregator (SUM/MIN/MAX/COUNT) becomes a collect table followed by an expressionNode that applies sum/min/max/count from the Zen built-in function library. Decision dependency chains declared via <informationRequirement><requiredDecision href="#decisionId"/></informationRequirement> become decisionNode references between separate decision files - each upstream decision is its own JSON graph, and the requiring graph references it through a decisionNode.

3. Translate expressions to Zen

[Both] FEEL operators map directly to Zen: ==, !=, <, >, <=, >=, and/or/not, ranges like [1..10] and (1..10). FEEL contains(?, "x") becomes Zen contains($, "x") in unary tests (where $ is the input column value). FEEL from json()/to json() are not needed - GoRules works on JSON natively. FEEL list functions (some, every, aggregations) map to Zen some, all, filter, map, sum, avg, min, max. [Camunda 7] JUEL expressions that call Spring or CDI beans (${myBean.method(input)}) cannot be translated by tooling - rewrite the bean logic inside a functionNode (ES6 JavaScript) or move the call to the calling application as a pre-processing step before invoking GoRules. JSR-223 cells (Groovy, Python, Ruby) must be rewritten in Zen or in functionNode JavaScript. [Camunda 8] BKMs (Business Knowledge Models, FEEL functions with parameters) become either a decisionNode (if the BKM is large enough to warrant its own graph) or a functionNode for reusable computation.

4. Handle constructs with no GoRules equivalent

[Camunda 7] FeelCustomFunctionProvider registrations (named FEEL functions added via CustomFunction.create()) have no global-function-library equivalent in GoRules - migrate each function into a functionNode. Spin XML variables (SpinXmlElement) do not migrate - convert XML to JSON in the calling application before invoking GoRules. TypedValue wire-format triples ({type, value, valueInfo}) must be unwrapped in the calling application; GoRules accepts plain JSON inputs. [Camunda 8] BKMs are not directly representable - decompose into separate decision files referenced by decisionNode, or inline as functionNode JavaScript. Custom FEEL functions in your own code become functionNode JavaScript in GoRules.

5. Separate the decision layer from the workflow layer

[Both] The BPMN process layer does not migrate to GoRules. Long-running processes with token state, timers, message correlation, user tasks, event subscriptions, multi-instance activities, and compensation events stay in a workflow orchestrator. Business rule tasks (zeebe:calledDecision in Camunda 8, DecisionService calls in Camunda 7) become HTTP calls from the orchestrator to GoRules at POST /projects/:projectId/evaluate/*. [Camunda 7] Java Delegates, Execution Listeners, Task Listeners, and Field Injection that contain decision logic move either to the calling application (most common - the application invokes GoRules and handles side effects itself) or into a functionNode if the logic is portable and side-effect-free. External Task workers stay in the workflow orchestrator. [Camunda 8] If you consume decision-instance evaluation records (via POST /v2/decision-instances/search), note that GoRules does not persist evaluations server-side - capture the per-call trace field in the calling application and persist it to your own audit store if needed.

6. Swap the API integration

[Camunda 8] Replace POST /v2/decision-definitions/evaluation with POST /projects/:projectId/evaluate/*. Authentication moves from Authorization: Bearer <OIDC JWT> to x-access-token: <project-access-token>. The request body changes from { "variables": {...} } to { "context": {...}, "trace": false }, and the response from { "evaluatedDecisions": [...], "output": ... } to { "performance": "...", "result": ..., "trace": {...} }. Search endpoints such as POST /v2/decision-instances/search have no analog - per-evaluation trace is response-only in GoRules. [Camunda 7] Replace POST /engine-rest/decision-definition/{id}/evaluate with the same POST /projects/:projectId/evaluate/* and move authentication to the x-access-token header. The variable wire format changes from {type, value, valueInfo} triples with capitalized type names (String, Long, Date, Object) and valueInfo.objectTypeName/serializationDataFormat on Object to plain JSON in a context object. Date values become ISO 8601 strings parsed by Zen date() functions.

7. Validate in parallel, then cut over

Run identical inputs through both engines and compare outputs. GoRules simulation returns a per-node trace - each node reports its input, output, and execution time - so divergences localize quickly. Once outputs match, publish the decision documents and release them to the target environment, then point the orchestrator or calling application at the GoRules endpoint.

GoRules AI

Let the copilot do the translation.

You can upload .dmn files to the AI copilot directly, whether exported from Camunda 8 (Web Modeler) or Camunda 7 (Desktop Modeler or deployment artifacts). What the copilot handles well, both versions: decision table structure (inputs, outputs, rules) maps to decisionTableNode regardless of source. FEEL-to-Zen translation is largely direct - == stays ==, > stays >, range syntax in [a..b] stays x in [a..b], contains(?, "x") becomes contains($, "x"), and FEEL list functions (some, every, sum, mean, count) map to Zen some, all, sum, avg, len(filter(...)). Decision dependency chains declared via informationRequirement become decisionNode references between separate decision files. What needs manual triage, Camunda 7 specifically: JUEL expressions referencing Spring or CDI beans (e.g. ${myBean.method(input)}) cannot be translated by tooling - the copilot cannot see your bean code. Rewrite the bean logic inside a functionNode (ES6 JavaScript) or move the call to the calling application as a pre-processing step. JSR-223 cells in DMN (Groovy, Python, Ruby) must be rewritten in Zen or functionNode JavaScript. FeelCustomFunctionProvider registrations have no global-function-library equivalent in GoRules - migrate each function into a functionNode. Spin XML variables (SpinXmlElement) do not migrate - convert XML to JSON in the calling application before invoking GoRules. What requires manual redesign, both versions: BKMs become either a decisionNode (graph composition) or a functionNode for inline reusable computation. The BPMN process layer - workflow orchestration, timers, message correlation, user tasks, External Tasks, event subscriptions, multi-instance activities - is out of scope for a decision engine entirely; it stays in a workflow orchestrator. GoRules does not persist per-evaluation audit records server-side; the per-call trace field is returned in the response but not stored, so persistent audit capture lives in the calling application.

Leave the build step
behind.

Bring your Camunda rules over, run both engines in parallel, and cut over when the outputs match.