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)
Host responsibilities
- Persistence
- Scheduling
- I/O
- Messaging
- Retries
- Transactions
01 — Findings
The gap in .NET BPMN tooling
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
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
Content-lossless interchange
Import diagnostics
BPMN DI preservation
Programmatic model builder
Token semantics
- 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
Zero dependency surface
04 — Code
Three things you can do today
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())}");
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
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
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
Tooling authors
Analysis and simulation
What paths exist through this process?
.NET teams needing BPMN interop
06 — Packages
Four packages
Bpmn.Model
The typed, immutable BPMN object model and execution-state records.
Base package. Everything else builds on it.
$dotnet add package Bpmn.ModelBpmn.Interchange
BPMN 2.0 XML reader, writer, import analyzer, and model builder.
Builds on Bpmn.Model.
$dotnet add package Bpmn.InterchangeBpmn.Semantics
The pure token-semantics interpreter.
Builds on Bpmn.Model. No I/O.
$dotnet add package Bpmn.SemanticsBpmn.Runtime.InMemory
A non-durable reference host with a virtual clock for simulation and tests.
Hosts Bpmn.Semantics.
$dotnet add package Bpmn.Runtime.InMemory07 — Getting started
Three steps
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.Semantics02 — 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())}");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.