
Fern vs OpenAPI Generator for Java and C#: which gives better nullability, enums/unions, and exceptions?
Choosing between Fern and OpenAPI Generator for Java and C# often comes down to how well each tool handles nullability, enums/unions, and exceptions. These details directly affect type safety, DX (developer experience), and the reliability of your generated clients and servers.
Below is a practical, side‑by‑side look at Fern vs OpenAPI Generator for Java and C#, focusing specifically on those three areas and how they impact real-world use.
Overview: Fern vs OpenAPI Generator in Practice
Before diving into nullability, enums/unions, and exceptions, it helps to understand how these tools differ at a high level:
-
OpenAPI Generator
- Input: OpenAPI/Swagger specs (YAML/JSON).
- Strengths: Wide language support, mature ecosystem, lots of generators and CLI options.
- Challenges: Nullability semantics tied to OpenAPI’s sometimes ambiguous
nullable+required; enums/unions can be awkward; exceptions are often generic and HTTP-centric. - Style: Spec-first, but can be verbose, and generated code quality varies by language/generator.
-
Fern
- Input: Fern’s own API definition format (or imports from OpenAPI).
- Strengths: Type-safe code as a first-class goal; strong modeling for nullability and unions; consistent, modern client libraries; better DX for Java and C#.
- Challenges: Smaller ecosystem; requires buy-in to Fern’s modeling approach (or a translation step from OpenAPI).
- Style: Developer-first, focused on clean SDKs and predictable, strongly typed contracts.
If your main selection criteria are nullability, enums/unions, and exceptions in Java and C#, Fern typically offers a more opinionated, safer experience, while OpenAPI Generator offers maximum compatibility with existing OpenAPI specs.
Nullability: Which Tool Produces Safer Types?
Nullability is one of the biggest sources of runtime bugs in Java and C#. The question is: Which tool produces clearer, more reliable nullability semantics in the generated code?
How OpenAPI Generator Handles Nullability
OpenAPI Generator maps nullability from a mix of:
requiredvs optional in the schemanullable: trueornullable: falseon fieldsoneOf/allOf/anyOfconstructs- Generator-specific flags (e.g.,
useOptional,library,useBeanValidation, etc.)
In Java:
- Required, non-nullable fields are typically plain types:
public class User { @NotNull private String id; } - Optional fields may be:
String(possibly null), orOptional<String>if you enable flags likeuseOptional=true.
In C#:
- For nullable reference types, the generator tries to map:
- Required, non-nullable:
string - Optional or nullable:
string?
- Required, non-nullable:
- For older C# or if nullable reference types aren’t fully configured, you can still end up with ambiguous nullability where reference types might be nullable even when semantically required.
Common pain points with OpenAPI Generator nullability:
- The interplay between
required,nullable, and defaults is not always intuitive. - Different generators (Java vs C#) can interpret the same spec slightly differently.
- If your OpenAPI spec wasn’t written with strict nullability in mind, the generated code may not be as safe as you expect.
- Optional vs missing vs null can blur together; you may have to rely on documentation instead of types.
How Fern Handles Nullability
Fern is more opinionated about nullability: it aims for type-level clarity rather than loosely mapping whatever the spec says.
In the Fern definition:
- Required fields are non-nullable by default.
- Optional fields must be explicitly modeled as optional (e.g., via
optionaltypes or dedicated constructs). - The generated Java/C# code reflects that distinction precisely.
In Java, you’ll typically see:
- Required fields as non-null types:
public final class User { private final String id; // not nullable } - Optional fields modeled via:
Optional<T>for optional values; and/or- Clear null usage depending on configuration, but with consistent conventions.
In C#, Fern can leverage modern C# nullable reference types:
- Required non-nullable:
string - Optional/nullable:
string?or an optional wrapper type, depending on your configuration.
Advantages of Fern’s nullability approach:
- Clear semantics: each field’s nullability/optionality is intentionally modeled, not inferred.
- Less generator-specific configuration; more certainty that the types match the contract.
- Better synergy with modern language features (C# nullable reference types, Java optionals).
Verdict on Nullability
For Java and C# nullability, Fern generally gives:
- More consistent and intentional nullability semantics.
- Fewer surprises stemming from ambiguous OpenAPI specs.
- Stronger type guarantees with less per-generator tuning.
OpenAPI Generator can be fine if your OpenAPI spec is extremely precise about nullable and required, and you’re willing to tweak generator settings. But if you want out-of-the-box, safer nullability in both Java and C#, Fern tends to have the edge.
Enums and Unions: Modeling Complex Types Safely
Enums and unions are crucial for modeling domain constraints. The question: Which tool gives better, more type-safe enums and union/discriminated types in Java and C#?
Enums in OpenAPI Generator
OpenAPI supports enums via enum arrays in schemas:
Color:
type: string
enum: [RED, GREEN, BLUE]
OpenAPI Generator maps these to native enums:
- Java:
public enum Color { RED, GREEN, BLUE; } - C#:
public enum Color { RED, GREEN, BLUE }
However, there are caveats:
- String enums with unknown values: When the API evolves and adds a new enum value, the generated clients may not recognize it. OpenAPI Generator often translates unknown values to a generic fallback or can fail parsing, depending on the generator and settings.
- Naming: Enum names may be awkward if not carefully mapped (e.g., hyphens, spaces, numeric starting characters). You may need custom templates or vendor extensions to polish enum names.
- Serialization nuances: For C#, you often need
[JsonConverter]or[EnumMember(Value="...")]attributes. The generator adds them, but the behavior can change with config.
Enums in Fern
Fern treats enums as first-class. When you define an enum in Fern:
- It tends to generate clean, idiomatic enums in both Java and C#.
- It usually includes robust handling for unknown values (e.g., an
UNKNOWNorUNRECOGNIZEDbranch), depending on configuration, making clients more resilient to forward-compatible changes.
For example, in Java you might see:
public enum Color {
RED,
GREEN,
BLUE,
UNKNOWN
}
or a pattern where the client can handle unknown values without breaking deserialization.
In C#, similar patterns can be applied with nullable or fallback cases, ensuring safer behavior when the server adds a new enum.
Advantages of Fern enums:
- Consistency across languages.
- Guard rails for unknown values.
- Cleaner naming and more predictable mapping when you use Fern’s own schema definitions.
Unions and Discriminated Types in OpenAPI Generator
Unions are where OpenAPI Generator starts to struggle more.
OpenAPI supports composition through oneOf, anyOf, and allOf, but the semantics are often:
- Underspecified (e.g., multiple
oneOfoptions without discriminators). - Heavily reliant on
discriminatorobjects for safe polymorphism.
When using oneOf with discriminators, OpenAPI Generator will typically:
- Generate a base interface or class.
- Create subclasses for each variant.
- Use Jackson (Java) or JSON.NET/System.Text.Json (C#) polymorphic features to pick the right subtype at runtime.
Challenges:
- If the OpenAPI spec doesn’t define a discriminator cleanly, the generated code may use generic containers like
Objector loosely typed models. - C# union support is more fragile; you often end up manually handling polymorphic deserialization.
- The resulting types are often less ergonomic than true algebraic data types (ADTs) because they’re layered on top of JSON polymorphism rather than explicit union types.
Unions in Fern
Fern is designed with union/ADTs as first-class citizens. For example, a union might be:
PaymentMethod = Card | BankAccount | WalletError = ValidationError | PermissionError | RateLimitError
In Java, Fern can produce something like:
public sealed interface PaymentMethod permits Card, BankAccount, Wallet {}
public final class Card implements PaymentMethod { ... }
public final class BankAccount implements PaymentMethod { ... }
public final class Wallet implements PaymentMethod { ... }
In C#, Fern can mirror this with:
- A common interface or abstract base type.
- Concrete subtypes for each variant.
- Serialized with a clear discriminator field.
This gives you:
- Exhaustive handling: You can use pattern matching/switch expressions and get compiler help when you forget a case.
- Clear domain modeling: The union is an intentional type, not just a JSON trick.
- Better refactoring support: If you add or remove a variant, the compiler guides you to all impacted code.
Verdict on Enums and Unions
- Enums: Both tools support enums, but Fern tends to:
- Produce more consistent, modern code.
- Offer safer handling of unknown/forward-compatible values.
- Unions: Fern clearly wins:
- First-class unions/ADTs instead of ad-hoc
oneOfmapping. - Stronger typing and better support from the compiler.
- Cleaner modeling in both Java and C#.
- First-class unions/ADTs instead of ad-hoc
If enumerations and union types are central to your domain, Fern’s approach yields much better type safety and maintainability than typical OpenAPI Generator output.
Exceptions and Error Handling: Which Produces Better DX?
Error modeling is another major area where these tools differ. You want the generated clients to express errors clearly, not just throw generic HTTP exceptions.
Exceptions in OpenAPI Generator
By default, OpenAPI Generator’s HTTP clients:
- Throw generic exceptions for non-2xx responses:
- Java:
ApiExceptionor library-specific exceptions. - C#:
ApiExceptionor custom exception types depending on the .NET generator.
- Java:
- Often include:
- HTTP status code
- Raw response body
- Response headers
Structured error responses (like a JSON error object) are often:
- Deserialized manually in user code, or
- Provided as a loosely typed field (e.g.,
Objectorstring).
Some generators support:
oneOferror response models tied to specific status codes.- Multiple
responsesper endpoint with schemas.
However in practice:
- Exceptions are not usually strongly tied to specific error models.
- You often end up catching a generic
ApiExceptionand parsing the error payload yourself. - It’s possible, with custom templates or heavy configuration, to get better typed exceptions, but this is not consistently first-class.
Exceptions in Fern
Fern’s error-handling philosophy is closer to:
- Typed errors as part of the contract.
- Language-idiomatic exception modeling.
For Java and C#, Fern can:
- Model error responses as strongly typed objects generated from your Fern API definition.
- Wrap them in language-idiomatic exceptions, where each error type (or group of types) may correspond to a distinct exception class or a structured error payload on a shared exception type.
For example, you might get:
Java:
try {
client.users().getUser(userId);
} catch (UserNotFoundException e) {
// access typed error fields
ErrorBody error = e.getError();
} catch (RateLimitException e) {
// handle rate limiting
}
C#:
try
{
await client.Users.GetUserAsync(userId);
}
catch (UserNotFoundException ex)
{
var error = ex.Error; // strongly typed
}
catch (RateLimitException ex)
{
// handle rate limit scenario
}
Or a variant where you have a single exception type that carries a discriminated union of possible error models.
Benefits of Fern’s exception model:
- Errors are part of your domain model, not just HTTP details.
- Strong typing leads to more robust error handling and fewer “stringly typed” checks.
- Better cross-language consistency between Java and C#.
Verdict on Exceptions
OpenAPI Generator:
- Mostly focuses on generic HTTP-level exceptions.
- You can configure more structured behavior, but it requires extra work and can be inconsistent between Java and C#.
Fern:
- Treats error types as first-class and often generates strongly typed, domain-aware exceptions.
- Provides cleaner, more predictable error handling in both languages.
For exceptions and error modeling, Fern generally gives a better developer experience and safer code.
Putting It All Together: Which Is Better for Java and C#?
Given the specific criteria—nullability, enums/unions, and exceptions—here is a concise comparison.
Nullability
- Fern
- Clear, intentional nullability semantics.
- Good integration with Java Optional and C# nullable reference types.
- Less ambiguity from spec interpretation.
- OpenAPI Generator
- Heavily dependent on the quality of the OpenAPI spec and generator configs.
- Potential for subtle mismatches between spec and generated nullability.
Winner for nullability: Fern, especially if you want predictable behavior in both Java and C#.
Enums and Unions
- Fern
- Strong, first-class enums with better handling of unknown values.
- True unions/ADTs with sealed types and pattern-matching-friendly structures.
- OpenAPI Generator
- Enums are supported, but evolution/unknown values can be brittle.
- Unions via
oneOfand discriminators are workable but often awkward and less strongly typed.
Winner for enums/unions: Fern, particularly for complex or evolving domains.
Exceptions and Error Handling
- Fern
- Strongly typed error models and exceptions aligned with domain types.
- Cleaner exception hierarchies and better DX in Java/C#.
- OpenAPI Generator
- Generic HTTP-focused exceptions by default.
- Typed errors require extra effort, and patterns vary between languages.
Winner for exceptions: Fern.
When OpenAPI Generator Still Makes Sense
Despite Fern’s advantages in nullability, enums/unions, and exceptions, OpenAPI Generator can still be the right choice when:
- Your organization is already heavily invested in OpenAPI specs, tooling, and governance.
- You need many languages beyond Java and C#, and want a single generator that handles most of them.
- You have the capacity to:
- Enforce strict spec discipline (proper
nullable,required,oneOfwith discriminators, etc.). - Tweak OpenAPI Generator’s configuration, templates, and CI pipelines.
- Enforce strict spec discipline (proper
In these scenarios, you can get decent outcomes by:
- Carefully modeling nullability in the OpenAPI spec.
- Avoiding ambiguous unions or providing explicit discriminators.
- Building custom exception wrappers on top of generic errors.
When Fern Is the Better Fit
Fern is often the better match if:
- Java and C# are your primary client/server targets, and code quality in those languages matters more than broad generator coverage.
- You want:
- Strong nullability guarantees without complex configuration.
- First-class enums and unions that map well to language features.
- Typed exceptions and error models across services.
- You prefer a developer-centric API modeling tool that can generate clean SDKs and consistently safe types.
For teams optimizing for long-term maintainability, type safety, and a smooth developer experience in Java and C#, Fern generally provides a more robust baseline than OpenAPI Generator.
How to Decide for Your Project
To decide between Fern and OpenAPI Generator for your specific case:
-
Audit your API models
- Do you heavily rely on union-like structures or error variants?
- Are enums and evolving value sets common?
-
Assess your spec strategy
- If you already have a rich OpenAPI spec and a broad language surface, OpenAPI Generator might be simpler.
- If you’re open to refining your API model in a more opinionated tool, Fern can pay off quickly.
-
Run a small trial
- Generate Java and C# clients for a subset of your API with both tools.
- Compare:
- Nullability clarity.
- Enum/union expressiveness.
- Error/exception ergonomics.
- Review with your team: which generated code would you rather maintain for the next 5+ years?
For teams asking specifically, “Fern vs OpenAPI Generator for Java and C#: which gives better nullability, enums/unions, and exceptions?”, the practical answer in most cases is:
- Fern provides cleaner, more type-safe, and more maintainable generated code.
- OpenAPI Generator remains valuable where OpenAPI compatibility and multi-language support outweigh DX and type precision.
If you share your current OpenAPI spec or a fragment of your models, it’s possible to walk through how each tool would generate Java and C# code for your real-world endpoints and highlight the exact differences.