Skip to main content

Command Palette

Search for a command to run...

ErrorOr vs OneOf vs FluentResults in .NET: Which Result Pattern Library Should Your Team Use in 2026?

Updated
β€’12 min read
ErrorOr vs OneOf vs FluentResults in .NET: Which Result Pattern Library Should Your Team Use in 2026?

Choosing a result pattern library in .NET has become one of those quietly loaded architectural decisions. ErrorOr, OneOf, and FluentResults all solve the same core problem β€” returning outcomes from methods without throwing exceptions β€” but they approach it from genuinely different angles. Picking the wrong one early means refactoring contracts across layers months later.

🎁 Want implementation-ready .NET source code you can drop straight into your project? Join Coding Droplets on Patreon for exclusive tutorials, premium code samples, and early access to new content. πŸ‘‰ https://www.patreon.com/CodingDroplets

This article compares ErrorOr, OneOf, and FluentResults across the dimensions that matter most in production ASP.NET Core codebases: type safety, error modeling, integration effort, readability, and long-term maintainability. The goal is a clear, opinionated recommendation β€” not a neutral shrug.

Why the Result Pattern Exists

Traditional .NET error handling relies on exceptions for control flow. Exceptions are expensive (stack unwinding, heap allocation), invisible in method signatures, and easy to forget to catch. In domain-heavy applications β€” especially those following Domain-Driven Design or Clean Architecture β€” exceptions used for expected business failures (a user not found, a duplicate email, an insufficient balance) create noise and slow down hot paths.

The result pattern replaces "throw on failure" with "return a typed value that represents either success or an error." The method signature becomes honest: callers can see that failure is possible, and the compiler can enforce that both outcomes are handled.

Three libraries dominate this space in the .NET ecosystem right now: FluentResults (the oldest, most feature-rich), OneOf (type-safe discriminated unions), and ErrorOr (opinionated, DDD-aligned, ASP.NET Core-friendly).

What Is the Result Pattern in C#?

The result pattern in C# means returning a wrapper type from a method instead of throwing an exception when something expected goes wrong. Rather than letting the call stack unwind, the caller receives a typed value that signals either success or one of several defined failure states.

This makes failures first-class in the method signature. A service returning ErrorOr<User> is telling callers β€” and the type system β€” that this operation can fail, and that callers must handle it. This is the foundation of railway-oriented programming in .NET.

All three libraries implement this idea, but their design priorities differ significantly.

FluentResults: The Feature-Rich Veteran

FluentResults has been around the longest and packs the most features. Its Result<T> type carries a list of IError and ISuccess objects, which allows accumulating multiple errors (useful for validation scenarios), attaching metadata, and building rich error hierarchies.

The API is fluent and discoverable. You create results with Result.Ok(value) and Result.Fail("message"), and consume them by inspecting .IsSuccess, .IsFailed, .Errors, and .Value. Error objects can carry reasons, metadata, and causes β€” essentially a hierarchical error tree.

Where FluentResults wins:

  • Accumulated errors β€” you can collect multiple failures in a single result
  • Rich error metadata β€” errors can carry structured data beyond a simple message
  • Good for validation pipelines where you want to surface all problems at once
  • Mature ecosystem and documentation

Where it falls short:

  • Not type-safe at the error level: errors are IError objects, not specific types the compiler enforces you handle
  • The generic Result<T> / Result split adds ceremony when you need unit return types
  • Missing first-class discriminated union semantics β€” you cannot pattern-match over specific error types exhaustively

Best fit: Applications where accumulated error reporting matters β€” think bulk operations, import pipelines, or complex validation workflows where you want to surface all problems at once rather than fail-fast.

OneOf: Type Safety as the Primary Goal

OneOf takes a fundamentally different approach. It implements discriminated unions for C# β€” a feature the language itself still lacks as of .NET 10. A OneOf<Success, NotFound, Conflict, Forbidden> is an algebraic type that is exactly one of those four types. The compiler (via exhaustive switch expressions or Match()) enforces that every case is handled.

This means error categories become real types in your domain model. Instead of catching Exception, switching on HttpStatusCode, or checking string messages, you write strongly-typed match expressions and the compiler will tell you if you add a new error case but forget to handle it somewhere.

Where OneOf wins:

  • Strongest type safety in the comparison β€” errors are specific types, not strings
  • Exhaustive pattern matching via Match() and Switch() β€” the compiler enforces handling all cases
  • Zero friction with modern C# switch expressions and pattern matching
  • Excellent for DDD domain model boundaries where error categories are meaningful

Where it falls short:

  • Verbose type signatures when you have many cases β€” OneOf<A, B, C, D, E> gets unwieldy
  • No built-in error metadata (severity, error codes, structured data)
  • No ASP.NET Core middleware or ProblemDetails integration out of the box
  • Accumulated errors require OneOf<Success, IReadOnlyList<Error>> workarounds

Best fit: Domain layers in Clean Architecture or DDD projects where precise error modeling matters and you want compile-time enforcement that every failure case is handled. Works especially well in CQRS command handlers.

ErrorOr: Opinionated and ASP.NET Core-Native

ErrorOr is the newest of the three and takes a strong, opinionated stance. Every result is either a value or a list of Error objects. Errors carry a type (ErrorType.Validation, ErrorType.NotFound, ErrorType.Conflict, ErrorType.Unauthorized, ErrorType.Unexpected, ErrorType.Failure) that maps directly to HTTP status codes.

This design is clearly shaped by ASP.NET Core API development. The built-in ErrorType enum was designed with ProblemDetails mapping in mind. Paired with the community pattern of IErrorOr-aware result mappers, you can translate service results to HTTP responses with minimal boilerplate.

ErrorOr also supports railway-style chaining via .Then(), .ThenAsync(), .Else(), and .Match() β€” allowing you to build pipelines that propagate errors without nested if-branches. The functional composition API makes it a natural fit for teams adopting functional patterns without going full F#.

Where ErrorOr wins:

  • Best ASP.NET Core integration β€” ErrorType maps cleanly to HTTP status codes and ProblemDetails
  • Functional chaining via .Then() / .ThenAsync() simplifies service layer logic
  • Accumulated errors out of the box (like FluentResults) while retaining typed error categories
  • Opinionated design reduces bikeshedding β€” the team doesn't need to debate error types
  • Clean, readable API surface compared to the alternatives

Where it falls short:

  • Less flexible for non-HTTP domains (no custom error metadata beyond the type enum and description)
  • Not as type-safe as OneOf at compile time β€” error types are an enum, not your own domain types
  • Relatively newer β€” smaller ecosystem, fewer community extensions

Best fit: ASP.NET Core Web API projects, Clean Architecture applications with CQRS, and teams that want convention over configuration for error handling. The sweet spot for most enterprise API teams in 2026.

Side-by-Side Comparison

Dimension FluentResults OneOf ErrorOr
Error type safety ❌ IError (not enforced) βœ… Compile-time types ⚠️ ErrorType enum
Accumulated errors βœ… Yes ⚠️ Workaround βœ… Yes
Functional chaining ⚠️ Limited ⚠️ Manual βœ… .Then() / .ThenAsync()
HTTP / ProblemDetails fit ⚠️ Manual mapping ⚠️ Manual mapping βœ… Built-in ErrorType
Exhaustive match ⚠️ No βœ… Yes ⚠️ Match() without exhaustion
Error metadata βœ… Rich (reasons, causes) ❌ None built-in ⚠️ Description string only
Learning curve Medium Low–Medium Low
.NET 10 compatibility βœ… βœ… βœ…
Maturity High High Medium

How Does Each Library Integrate with ASP.NET Core APIs?

Integration with the ASP.NET Core request pipeline is where the three libraries diverge most noticeably in day-to-day development.

FluentResults requires you to write your own mapping from IError lists to ProblemDetails responses. There is no built-in convention for which error maps to which HTTP status code β€” you build it. This is fine for experienced teams, but it adds ceremony and creates room for inconsistency.

OneOf has no built-in HTTP awareness at all. You pattern-match the result in your controllers or minimal API endpoints and return Results.NotFound(), Results.Conflict(), or whatever the outcome calls for. This keeps the library pure and focused, but the HTTP mapping layer is entirely your responsibility. For teams using the IResult / Minimal API pattern, this is manageable β€” but repetitive.

ErrorOr was designed with HTTP in mind. The ErrorType enum variants have clear HTTP semantics: NotFound β†’ 404, Conflict β†’ 409, Unauthorized β†’ 401, Validation β†’ 422. With a thin mapping helper (a single extension method or a base controller), you can translate any ErrorOr<T> to a ProblemDetails response consistently across your entire API. Chapter 6 of the ASP.NET Core Web API: Zero to Production course shows exactly this pattern β€” wiring global error handling together with typed result propagation across layers.

Which Should Your .NET Team Use in 2026?

For the majority of enterprise ASP.NET Core teams building production APIs in 2026, ErrorOr is the pragmatic choice. It provides accumulated error support, functional chaining, good HTTP alignment, and a readable API β€” without requiring a deep functional programming background.

Choose FluentResults if:

  • Your domain produces multiple errors simultaneously (bulk validation, import pipelines)
  • You need rich, hierarchical error metadata with causes and reasons
  • Your team is already invested in the FluentResults ecosystem

Choose OneOf if:

  • You are building a pure domain model in Clean Architecture or DDD where compile-time exhaustiveness is a core requirement
  • Your team is comfortable with algebraic types and match expressions
  • You want the type system to prevent you from silently ignoring error cases

Choose ErrorOr if:

  • You are building an ASP.NET Core API and want error-to-HTTP mapping with minimal boilerplate
  • You want functional chaining (.Then() / .ThenAsync()) for clean service layer pipelines
  • You want an opinionated default that reduces architectural debates within the team

Avoid mixing all three across the same codebase. Pick one and standardize. Consistency at scale matters more than picking the theoretically optimal library.

What About Custom Result Types?

Some senior .NET architects roll their own result type. For small teams with specific requirements, this is a valid choice β€” a bespoke Result<T> record with domain-specific error modeling can be leaner than any library. The trade-off is that you own the maintenance, the chaining API, and the HTTP mapping. For most teams, adopting ErrorOr or OneOf is a better use of time.

Does the Result Pattern Replace All Exception Handling?

No β€” and this is a critical distinction. The result pattern is for expected failures: business rule violations, not-found scenarios, validation errors, and conflict states. Unexpected failures β€” infrastructure outages, null reference bugs, unhandled edge cases β€” should still throw exceptions and be caught at the application boundary by global exception handling middleware.

A well-designed ASP.NET Core service uses both: result types for domain-layer flows, and IExceptionHandler / ProblemDetails middleware for catastrophic failures. These are complementary, not competing, strategies.

β˜• Prefer a one-time tip? Buy us a coffee β€” every bit helps keep the content coming!

Frequently Asked Questions

Is ErrorOr better than OneOf for ASP.NET Core Web APIs? For most Web API projects, yes. ErrorOr's built-in ErrorType enum maps cleanly to HTTP status codes and ProblemDetails, and its .Then() / .ThenAsync() chaining reduces boilerplate in service layers. OneOf is more type-safe at compile time but requires manual HTTP mapping.

Can I use OneOf with Clean Architecture and CQRS in .NET? Absolutely. OneOf is an excellent fit for the domain and application layers in Clean Architecture. Command handlers return OneOf<Success, NotFound, Conflict> and callers are forced by the type system to handle every case. The HTTP mapping layer lives at the presentation boundary, keeping domain logic pure.

Does the result pattern eliminate the need for exceptions in C#? No. The result pattern handles expected failures β€” business rule violations, not-found scenarios, validation errors. Unexpected failures (bugs, infrastructure failures) should still throw exceptions and be handled by global exception middleware. Both mechanisms belong in a well-designed ASP.NET Core application.

Is FluentResults still worth using in .NET 10? Yes, for specific scenarios. FluentResults shines when you need to accumulate multiple errors simultaneously (bulk validation, data import) or attach rich error metadata with cause chains. For straightforward API projects, ErrorOr is typically a simpler choice in 2026.

What is railway-oriented programming in .NET? Railway-oriented programming is a functional pattern where operations are chained as a sequence of steps. If any step produces a failure, execution "switches tracks" and subsequent success-path steps are skipped. ErrorOr's .Then() / .ThenAsync() methods implement this pattern in C#, allowing service layer pipelines that propagate errors without deeply nested if-else branches.

Should .NET teams build custom Result types instead of using a library? For most teams, no. Libraries like ErrorOr and OneOf are battle-tested, well-documented, and actively maintained. Building a custom result type adds maintenance overhead without meaningful benefit unless your domain has requirements that none of the available libraries can satisfy.

Which result pattern library is best for DDD (Domain-Driven Design) projects? OneOf is the strongest fit for rich domain models because its compile-time exhaustive matching aligns with explicit error modeling in DDD. ErrorOr works well too, especially when the domain layer feeds directly into an API response. FluentResults is a good choice when domain validations need to accumulate multiple errors at once.