Skip to content

MIT licensedZero dependenciesnet8.0 & net10.0

A BPMN interpreter for .NET. Not a workflow engine.

A typed, immutable BPMN 2.0 object model, a content-lossless XML reader and writer, and a deterministic token-semantics interpreter. MIT licensed. Zero dependencies. net8.0 and net10.0.

$dotnet add package Bpmn.Interchange

(process definition, current token state, one event)
(next state, commands for the host)

PROCESS · order-intakeINTERPRETED, NOT EXECUTEDSTARTValidatetaskXORApprove orderuserTaskReject orderserviceTaskENDSEMANTICS CORE · PURE FUNCTION
(definition, tokenState, event)
(nextState, commands[]) — the host performs the work

Host responsibilities

  • Persistence
  • Scheduling
  • I/O
  • Messaging
  • Retries
  • Transactions

01 — Findings

The gap in .NET BPMN tooling

A survey of 88 C# repositories and 50 NuGet packages found no maintained, license-clean .NET library that does BPMN 2.0 properly.
  • The only full BPMN engine is GPL-3.0-or-later.

  • The closest interchange library ships as a closed-source binary and hard-depends on System.Drawing.Common, which throws on non-Windows platforms.

  • Camunda and Zeebe .NET clients do not parse BPMN. They transfer .bpmn definitions as opaque data.

  • No generated-from-XSD BPMN object model is published independently on NuGet.

This is a verifiable hole in the ecosystem. That is the whole reason this library exists.

02 — Scope

What it is, and what it is not

Both columns are the product. The boundary is deliberate: the host performs work, the library interprets BPMN semantics.

What it is

  • BPMN 2.0 XML reader and writer over a typed, immutable object model.
  • Content-lossless round-tripping.
  • Foreign extensionElements are retained, including Camunda, Zeebe, Flowable, and unknown namespaces.
  • BPMN DI layout is retained: shapes, edges, waypoints, and label bounds.
  • Import analysis with element-scoped Info, Degraded, and Dropped diagnostics.
  • Analyze and commit share one implementation path, so a dry run cannot disagree with the real import.
  • Processes can be built programmatically.
  • Token-semantics interpreter.
  • Deterministic and synchronous.
  • Every shipped package has zero external dependencies.

What it is not

  • Not a production workflow engine.
  • No durable persistence.
  • No scheduler.
  • No retries.
  • No message broker.
  • No job queue.
  • No distributed coordination.
  • It does not execute work.
  • No expression evaluator: no FEEL, JUEL, or JavaScript.
  • Not DMN.
  • Not CMMN.
  • Not a modeler.
  • Not a renderer.
  • Not a Camunda, Zeebe, or Flowable client.
  • Not an XSD schema validator.
  • Not byte-exact on round-trip: content is preserved, formatting is not.

03 — Capabilities

Features

Typed immutable model

A neutral BPMN object model suitable for reading, analysis, transformation, generation, and interpretation.

Content-lossless interchange

Read, modify, and write BPMN while retaining foreign vendor extensions that the library itself does not understand.

Import diagnostics

Element-scoped Info, Degraded, and Dropped findings. Analysis and import use the same path so preview and commit cannot drift.

BPMN DI preservation

Preserve diagram shapes, edges, waypoints, and label bounds across round-trips.

Programmatic model builder

Construct BPMN process definitions directly in C# without starting from XML.

Token semantics

Support semantics for:
  • exclusive gateways
  • parallel gateways
  • inclusive gateways
  • event-based gateways
  • start events
  • intermediate events
  • end events
  • interrupting boundary events
  • non-interrupting boundary events
  • embedded subprocesses
  • event subprocesses
  • multi-instance
  • compensation
  • transactions
  • escalation
  • cyclic flows

Deterministic execution

Any evaluation can be reproduced from four JSON values. The same inputs always produce the same result.

Zero dependency surface

Every shipped package has zero external NuGet dependencies. Targets net8.0 and net10.0.

04 — Code

Three things you can do today

Read and analyze a definition, build one in C#, or step a process forward against a virtual clock.

Read a .bpmn file

Read a .bpmn file
using Bpmn.Interchange;

// Analyze and Read share one code path, so a dry run cannot drift from the real one.
var result = new BpmnXmlReader().Read(File.ReadAllText("order-intake.bpmn"));

foreach (var issue in result.Analysis.Issues)
    Console.WriteLine($"{issue.Severity,-8} {issue.ElementId ?? "-",-24} {issue.Message}");

var definitions = result.Definitions;
Console.WriteLine($"{definitions.Processes.Count} process(es), {result.Analysis.Issues.Count} finding(s)");

// Vendor annotations other readers discard are still here.
foreach (var element in definitions.Processes.SelectMany(p => p.Elements))
    if (!element.Extensions.IsEmpty)
        Console.WriteLine($"{element.ElementId}: retained {string.Join(", ", element.Extensions.RetainedNamespaces())}");

Build a process in code

Build a process in code
using Bpmn.Interchange;
using Bpmn.Model;

var definitions = new BpmnDefinitionsBuilder()
    .TargetNamespace("http://valence.works/orders")
    .Process("order-intake", process => process
        .StartEvent("start")
        .ExclusiveGateway("large-order")
        .UserTask("manual-review", "Manual review")
        .EndEvent("accepted")
        .Connect("start", "large-order")
        .Connect("large-order", "manual-review", condition: "large")
        .Connect("large-order", "accepted", isDefault: true)
        .Connect("manual-review", "accepted"))
    .Build();

// Layout is synthesized where the model carries none, and every edge gets at least
// two waypoints, so the output opens in a BPMN modeler without complaint.
File.WriteAllText("order-intake.bpmn", new BpmnXmlWriter().Write(definitions));

Simulate with a virtual clock

Simulate with a virtual clock
using Bpmn.Interchange;
using Bpmn.Runtime.InMemory;

// A reference host: virtual clock, single process, nothing durable.
var definitions = new BpmnXmlReader().Read(File.ReadAllText("order-intake.bpmn")).Definitions;

var host = new InMemoryBpmnHost();
var instance = host.Start(definitions.Processes[0]);

instance.CompleteWork("node-manual-review");
instance.Clock.Advance(TimeSpan.FromDays(7)); // a seven-day timer resolves in microseconds

foreach (var work in instance.PendingWork)
    Console.WriteLine($"waiting on {work.ElementId}");

Console.WriteLine(instance.IsCompleted ? $"completed: {instance.Outcome}" : "still running");

// Every evaluation is recorded, so you can see exactly what the interpreter decided.
Console.WriteLine(instance.Transcript);

05 — Audience

Who this is for

Engine builders

You are writing the durable engine. Take the semantics. Keep your own persistence, scheduling, retries, queues, and infrastructure.

Tooling authors

Linters, converters, documentation generators, migration tools, and model transformation utilities. Lossless round-tripping lets your tool change what it owns while retaining what it does not.

Analysis and simulation

Walk a process, generate test paths, inspect reachability, analyze behavior, or test scenarios without standing up a production workflow runtime.Can this state reach that task?
What paths exist through this process?

.NET teams needing BPMN interop

You receive .bpmn files authored with tools such as Camunda, Zeebe, or Flowable Modeler and need to inspect or transform them from C# without throwing vendor-specific extensions away.

06 — Packages

Four packages

Every shipped package has zero external dependencies and targets net8.0 and net10.0.

Bpmn.Model

The typed, immutable BPMN object model and execution-state records.

Base package. Everything else builds on it.

$dotnet add package Bpmn.Model

Bpmn.Interchange

BPMN 2.0 XML reader, writer, import analyzer, and model builder.

Builds on Bpmn.Model.

$dotnet add package Bpmn.Interchange

Bpmn.Semantics

The pure token-semantics interpreter.

Builds on Bpmn.Model. No I/O.

$dotnet add package Bpmn.Semantics

Bpmn.Runtime.InMemory

A non-durable reference host with a virtual clock for simulation and tests.

Hosts Bpmn.Semantics.

$dotnet add package Bpmn.Runtime.InMemory

07 — Getting started

Three steps

  1. 01 — Install

    Add the interchange package

    Start with reading and writing BPMN. Add the semantics package when you need interpretation.

    $dotnet add package Bpmn.Interchange
    $dotnet add package Bpmn.Semantics
  2. 02 — Read

    Read a BPMN file

    The reader returns the definitions together with the analysis of the import.

    Read a .bpmn file
    using Bpmn.Interchange;
    
    // Analyze and Read share one code path, so a dry run cannot drift from the real one.
    var result = new BpmnXmlReader().Read(File.ReadAllText("order-intake.bpmn"));
    
    foreach (var issue in result.Analysis.Issues)
        Console.WriteLine($"{issue.Severity,-8} {issue.ElementId ?? "-",-24} {issue.Message}");
    
    var definitions = result.Definitions;
    Console.WriteLine($"{definitions.Processes.Count} process(es), {result.Analysis.Issues.Count} finding(s)");
    
    // Vendor annotations other readers discard are still here.
    foreach (var element in definitions.Processes.SelectMany(p => p.Elements))
        if (!element.Extensions.IsEmpty)
            Console.WriteLine($"{element.ElementId}: retained {string.Join(", ", element.Extensions.RetainedNamespaces())}");
    
  3. 03 — Inspect

    Inspect diagnostics

    Import findings tell the caller exactly which elements were fully understood, which were degraded, and which were dropped. Each finding is scoped to an element id, so nothing is silently lost.

    • Info
    • Degraded
    • Dropped

08 — FAQ

Questions

Can I run production workflows on this?

No. BPMN for .NET interprets BPMN semantics. It does not provide durable persistence, scheduling, retries, queues, or distributed runtime infrastructure.

Does it evaluate conditions?

No expression evaluator ships with the library. Conditions remain opaque expressions that the host can pass to its own evaluator.

Will round-tripping change my BPMN file?

Formatting may change. Content is preserved. The goal is content-lossless round-tripping, not byte-identical XML.

Does it retain Camunda and Zeebe extensions?

Yes. Foreign extension elements are preserved, including BPMN DI layout.

Does it support DMN?

No. DMN and CMMN are outside the scope of this project.

What is the license?

MIT.