Repository navigation
Activity Exception Handling
Activities support graph-level exception handling in addition to the runtime IActivityExceptionHandler observability hook. Graph handlers are built as exception-handler nodes and can provide an alternate path when an action, accepted-event action, time-event action, or flow guard/transformation throws.
Call AddExceptionHandler<TException> in the builder callback for the node that should be protected. The handler receives an IExceptionHandlerContext<TException> with the exception, Activity node context, cancellation token, and output-token API:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Stateflows;
using Stateflows.Activities;
public sealed record ReservationOutcome(string Status);Inside the host's registration code, with application services and orderId in scope:
builder.Services.AddStateflows(stateflows => stateflows
.AddActivities(activities => activities
.AddActivity("OrderProcessing", activity => activity
.AddInitial(node => node.AddControlFlow("ReserveStock"))
.AddAction(
"ReserveStock",
async context =>
{
await inventory.ReserveAsync(orderId, context.CancellationToken);
context.Output(new ReservationOutcome("reserved"));
},
node => node
.AddFlow<ReservationOutcome>("Complete")
.AddExceptionHandler<InvalidOperationException>(async exceptionContext =>
{
logger.LogWarning(
exceptionContext.Exception,
"Could not reserve stock for order {OrderId}",
orderId);
exceptionContext.Output(new ReservationOutcome("reservation-failed"));
}))
.AddAction("Complete", async context =>
await orders.MarkProcessedAsync(orderId, context.CancellationToken),
node => node.AddControlFlow<FinalNode>())
.AddFinal())));In this example the action's output flow carries either the success or handled-failure outcome to Complete; the interrupted action does not resume. For broader coverage, register a handler on a containing Structured, Parallel, or Iterative Activity node; that can handle exceptions escaping descendant nodes in its nested scope.
The builder also supports a class-based handler:
using System;
using System.Threading;
using System.Threading.Tasks;
using Stateflows.Activities;
public sealed class InvalidOperationHandler : IExceptionHandlerNode<InvalidOperationException>
{
public Task HandleAsync(
InvalidOperationException exception,
CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}Register it on the protected node with .AddExceptionHandler<InvalidOperationException, InvalidOperationHandler>(). The handler type is created through Stateflows' model-element activator, so it can use constructor injection. The non-generic IExceptionHandlerNode is a convenience for handlers that work with any Exception.
When an exception occurs, Stateflows searches handlers attached to the node where it originated, then walks outward through containing structured Activity nodes. The nearest containing scope with a matching handler handles it. Within that scope, an exact exception-type match is preferred; otherwise a handler whose declared exception type is a base type of the thrown exception can match. For example, a handler for IOException can handle FileNotFoundException.
The exception is available as context.Exception. IExceptionHandlerContext<TException> also implements the Activity node context and output-token interfaces. A class handler receives the typed exception and cancellation token through HandleAsync.
Handlers added to a node are executable graph elements created by the fluent AddExceptionHandler API; users do not connect them with ordinary flows. An exception handler's output tokens are forwarded into the enclosing execution scope. Graph handlers cover failures during node execution and flow guard/transformation logic; they do not handle Activity or node initialization/finalization callbacks. Those lifecycle failures are reported through IActivityExceptionHandler and remain failed. When no graph handler matches, normal exception propagation continues, including runtime notifications through IActivityExceptionHandler where applicable.
These two APIs have different purposes:
| API | Role | Does it recover the Activity execution? |
|---|---|---|
AddExceptionHandler<TException>(...) / IExceptionHandlerNode<TException>
|
Model a local or enclosing-scope exception-handling path in the Activity graph | Yes, when a matching graph handler completes successfully |
IActivityExceptionHandler |
Observe/report failures from Activity initialization/finalization, node operations, and flow guards/transformations | No. It acknowledges/report exceptions but the failed execution remains failed |
See Observability for runtime exception hooks and their limits. Activity exception handlers are separate from State Machine exception handlers, which can acknowledge failures or allow them to be sent back to a State Machine as exception events.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026
Overview
Installation
Runtimes
Orleans runtime
Concepts
Events and Structural Events
Messaging Patterns
Validation
Scheduling
Observability
Behaviors
Behavior Values
Dependency Injection
Behavioral Groups
State Machines
Concepts
State Identity
In-State Event Deferrals
Evaluation of Transitions
Exception Handling
Building blocks
Regions
States
State
Composite State
Orthogonal State
Final State
Pseudostates
Choice
Junction
Fork
Join
History
Transitions
Transition
Default Transition
Internal Transition
Activities
Concepts
Exception Handling
Implicit Fork and Join
Tokens
Building blocks
Nodes
Action Node
Decision Node
Merge Node
Initial Node
Final Node
Input Node
Output Node
Fork Node
Join Node
Accept Event Action Node
Send Event Action Node
Publish Event Action Node
Time Event Action Node
Data Store Node
Structured Activity Node
Iterative Activity Node
Parallel Activity Node
Flows
Data Flow
Control Flow
Actions
Entities
Fields
Mutations
Projections
Extensions
ClearScript
FluentValidation
JSON definitions
Minimal APIs
OneOf
OpenTelemetry
PlantUML
Scheduling