Skip to main content

Optional Nullable Support

The liblab SDK generator provides sophisticated support for handling optional and nullable fields across multiple programming languages. This feature allows for precise control over field presence, absence, and null values in your API models, ensuring type safety and clear semantics in the supported languages.

Understanding the Four Field Types

When working with OpenAPI specifications, fields can be categorized into four distinct types based on their required status and nullable property:

  1. Required Non-Nullable: Field must be present and cannot be null
  2. Required Nullable: Field must be present but can be null
  3. Optional Non-Nullable: Field can be omitted but if present, cannot be null
  4. Optional Nullable: Field can be omitted or present with any value (including null)

OpenAPI Schema Definition

Here's how these field types are defined in your OpenAPI specification:

{
"schemas": {
"ModelWithOptionalNullableDefaultValues": {
"type": "object",
"required": [
"requiredString",
"requiredInteger",
"requiredNullableString",
"requiredNullableInteger"
],
"properties": {
"requiredString": {
"type": "string"
},
"requiredInteger": {
"type": "integer",
"format": "int64"
},
"requiredNullableString": {
"type": "string",
"nullable": true
},
"requiredNullableInteger": {
"type": "integer",
"format": "int64",
"nullable": true
},
"optionalString": {
"type": "string"
},
"optionalInteger": {
"type": "integer",
"format": "int64"
},
"optionalNullableString": {
"type": "string",
"nullable": true
},
"optionalNullableInteger": {
"type": "integer",
"format": "int64",
"nullable": true
}
}
}
}
}

Language-Specific Implementations

Java Implementation

The Java SDK uses JsonNullable wrapper types for optional fields, providing clear distinction between undefined, null, and present values.

Generated Model

@Data
@Builder
@With
@ToString
@EqualsAndHashCode
@Jacksonized
public class ModelWithOptionalNullableDefaultValues {

// Required Non-Nullable
@NonNull
private String requiredString;

@NonNull
private Long requiredInteger;

// Required Nullable
@JsonInclude(JsonInclude.Include.ALWAYS)
private String requiredNullableString;

@JsonInclude(JsonInclude.Include.ALWAYS)
private Long requiredNullableInteger;

// Optional Non-Nullable (using JsonNullable wrapper)
@JsonProperty("optionalString")
private JsonNullable<String> optionalString;

@JsonProperty("optionalInteger")
private JsonNullable<Long> optionalInteger;

// Optional Nullable (using JsonNullable wrapper)
@JsonProperty("optionalNullableString")
private JsonNullable<String> optionalNullableString;

@JsonProperty("optionalNullableInteger")
private JsonNullable<Long> optionalNullableInteger;

// Convenience getters that return unwrapped values
@JsonIgnore
public String getOptionalString() {
return optionalString.orElse(null);
}

@JsonIgnore
public Long getOptionalInteger() {
return optionalInteger.orElse(null);
}

@JsonIgnore
public String getOptionalNullableString() {
return optionalNullableString.orElse(null);
}

@JsonIgnore
public Long getOptionalNullableInteger() {
return optionalNullableInteger.orElse(null);
}
}

Usage Examples

// Example 1: Creating an object with all field types
ModelWithOptionalNullableDefaultValues model =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString") // Required Non-Nullable
.requiredInteger(8L) // Required Non-Nullable
.requiredNullableString("not null") // Required Nullable
.requiredNullableInteger(null) // Required Nullable (can be null)
.optionalString("optionalString") // Optional Non-Nullable
.optionalInteger(9L) // Optional Non-Nullable
.optionalNullableString("optional") // Optional Nullable
.optionalNullableInteger(null) // Optional Nullable (can be null)
.build();

// Example 2: Optional fields omitted entirely
ModelWithOptionalNullableDefaultValues minimalModel =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString")
.requiredInteger(8L)
.requiredNullableString(null) // Still required, but can be null
.requiredNullableInteger(null) // Still required, but can be null
// Optional fields omitted - they will be omitted from JSON when serialized and sent to API
.build();

// Example 3: Optional fields with mixed values
ModelWithOptionalNullableDefaultValues mixedModel =
ModelWithOptionalNullableDefaultValues.builder()
.requiredString("requiredString")
.requiredInteger(8L)
.requiredNullableString("not null")
.requiredNullableInteger(null)
.optionalString("present") // Optional non-nullable with value
.optionalNullableString(null) // Optional nullable set to null
// optionalInteger and optionalNullableInteger omitted
.build();

// What the server receives for Example 1 (all fields set):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "not null",
// "requiredNullableInteger": null,
// "optionalString": "optionalString",
// "optionalInteger": 9,
// "optionalNullableString": "optional",
// "optionalNullableInteger": null
// }

// What the server receives for Example 2 (minimal model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": null,
// "requiredNullableInteger": null
// // Note: optional fields are completely omitted from JSON
// }

// What the server receives for Example 3 (mixed model):
// {
// "requiredString": "requiredString",
// "requiredInteger": 8,
// "requiredNullableString": "not null",
// "requiredNullableInteger": null,
// "optionalString": "present",
// "optionalNullableString": null
// // Note: optionalInteger and optionalNullableInteger are omitted
// }

Key Features

  • Clear Semantics: JsonNullable clearly distinguishes between undefined, null, and present values
  • Validation: Builder pattern validates required fields and prevents invalid null assignments
  • JSON Serialization: Proper handling of optional fields in JSON serialization/deserialization

Key Benefits

  • Cross-Language Consistency: Similar patterns and behaviors across all supported languages
  • Clear Semantics: Explicit distinction between undefined, null, and present values
  • API Contract Compliance: Ensures generated SDKs properly handle all OpenAPI field types
  • Developer Experience: Intuitive APIs that match each language's idioms and conventions

Best Practices

  1. Always specify required fields: Mark fields as required in your OpenAPI spec when they must be present
  2. Use nullable appropriately: Only mark fields as nullable when they can legitimately be null
  3. Test edge cases: Verify behavior with undefined, null, and present values
  4. Document field requirements: Clearly document which fields are optional vs required in your API documentation
  5. Validate on the server: Always validate field presence and nullability on your API server, regardless of client-side handling