Speakeasy generates Java SDKs that integrate naturally with existing Java ecosystems, following established conventions for consistency with hand-written libraries. The generated code provides full IDE support, compile-time validation, and seamless integration with standard tooling.
Design principles:
Native Java ergonomics: Code generation leverages Java’s type system, generics, and method chaining to create APIs that feel natural to Java developers. Builder patterns, fluent interfaces, and standard library types eliminate the need to learn framework-specific abstractions
Comprehensive type safety: Strong typing catches API contract violations at compile time, while JSR-305/Jakarta nullability annotations provide rich IDE warnings and autocomplete derived directly from your OpenAPI specification
Flexible concurrency models: Synchronous execution by default supports traditional blocking patterns, while .async() mode provides CompletableFuture<T> and reactive streams support for non-blocking architectures—enabling incremental adoption without rewriting existing code
Minimal runtime dependencies: Built on Java standard library primitives like CompletableFuture<T> and Flow.Publisher<T> rather than heavyweight frameworks, ensuring clean integration into existing codebases and microservice architectures
Specification fidelity: Method signatures, documentation, and validation rules generated directly from OpenAPI definitions maintain accuracy between API contracts and client code, reducing integration surprises
Core Features
Type Safety & Null Handling
The SDK provides compile-time validation and runtime checks for required fields, with intuitive null handling:
Compile-time validation: Strong typing catches problems before runtime
Runtime validation: Required fields throw exceptions if missing
Null-friendly setters: Simple setters without Optional/JsonNullable wrapping
Smart getters: Return types match field semantics - direct access for required fields, Optional<T> for non-required fields, and JsonNullable<T> for non-required nullable fields
Fluent Call-Builder Chaining
The SDK supports fluent method chaining that combines method builders with request builders for intuitive API calls:
Authentication & Security
OAuth flows and standard security mechanisms
Custom enum types using string or integer values
All-field constructors for compile-time OpenAPI change detection
Synchronous Methods
Basic Methods
Synchronous methods are the default mode for all SDK calls:
Pagination
For synchronous pagination, use .call(), .callAsIterable(), .callAsStream(), or .callAsStreamUnwrapped():
.call(): Returns the first page only
.callAsIterable(): Returns Iterable<> for for-each iteration with automatic paging
.callAsStream(): Returns java.util.Stream of pages with automatic paging
.callAsStreamUnwrapped(): Returns java.util.Stream of concatenated items from all pages
Server-Sent Events
For synchronous SSE, use the events() method with try-with-resources:
Error Handling
Check the status code of the response. If it’s an error response, the error field contains the decoded error value.
Coming Soon
Support for throwing unsuccessful status codes as exceptions is coming soon.
Asynchronous Methods
Dual SDK Architecture
Speakeasy Java SDKs implement a dual interface pattern that provides both synchronous and asynchronous programming models without breaking changes:
Synchronous by default: All SDK instances work synchronously out of the box, maintaining compatibility with existing code.
Async opt-in: Call .async() on any SDK instance to switch to asynchronous mode for that method chain.
Consistent API: The same methods and parameters work in both modes, only the return types differ.
Non-blocking I/O implementation
The async implementation uses Java 11’s HttpClient async APIs and NIO.2 primitives for end-to-end non-blocking method calls:
HTTP requests: HttpClient.sendAsync() with CompletableFuture<HttpResponse<T>>
File I/O: AsynchronousFileChannel for non-blocking file methods
Stream processing: Flow.Publisher<List<ByteBuffer>> for efficient byte handling
Reactive Streams integration
For async iterables, the SDK leverages Reactive Streams Publisher<T> to provide:
Backpressure handling: Consumers control the rate of data processing
Ecosystem interoperability: Works with Project Reactor , RxJava, Akka Streams, and other reactive libraries
Resource efficiency: Memory-efficient processing of large datasets
Composition: Chain and transform async streams declaratively
The examples in this documentation use Flux from Project Reactor to demonstrate interoperability with reactive frameworks.
The SDK implements custom publishers, subscribers, and subscriptions using JDK-native operators while maintaining lightweight dependencies.
Async Pagination
For async pagination, use callAsPublisher() and callAsPublisherUnwrapped() methods that return reactive streams:
Async Server-Sent Events
For async SSE, EventStream implements Publisher<EventType> directly:
Migration & DevX Improvements
Async-enabled SDKs provide backward compatibility, gradual adoption via .async() calls, and compatibility with Java 21+ virtual threads. Additional enhancements include null-friendly parameters, Jakarta annotations, enhanced error handling, and built-in timeout/cancellation support.
Package Structure
build.gradle
build-extras.gradle
gradlew
settings.gradle
...
Advanced Topics
Blob Abstraction
The Blob class provides efficient byte-stream handling across both sync and async methods:
HTTP Client Customization
The Java SDK HTTP client is configurable and supports both synchronous and asynchronous methods:
A default implementation based on java.net.HttpClient provides sync and async patterns with connection pooling and streaming support.
Data Types & Serialization
The SDK uses native Java types where possible and provides custom handling for complex OpenAPI constructs.
Primitive and Native Types
Where possible, the Java SDK uses native types and primitives to increase null safety:
java.lang.String
java.time.OffsetDateTime for date-time format
java.time.LocalDate for date format
java.math.BigInteger for unlimited-precision integers
java.math.BigDecimal for unlimited-precision decimals
int (or java.lang.Integer)
long (or java.lang.Long)
float (or java.lang.Float)
double (or java.lang.Double)
boolean (or java.lang.Boolean)
Unlimited-Precision Numerics
For applications requiring high-precision decimal or integer types (such as monetary amounts), use format specifications:
Both map to java.math.BigInteger and java.math.BigDecimal respectively, with convenient builder overloads:
Union Types
Support for polymorphic types uses OpenAPI’s oneOf keyword with different strategies based on discriminator presence.
Non-discriminated oneOf uses composition:
Discriminated oneOf uses inheritance:
oneOf Type Erasure Handling:
When generic types would conflict, suffixed factory methods are generated:
anyOf Support:
The anyOf keyword is treated as oneOf with forgiving deserialization—when multiple subtypes match, the heuristic selects the subtype with the greatest number of matching properties.
Enums
Closed Enums (standard Java enum):
Open Enums with x-speakeasy-unknown-values: allow:
Generates a concrete class instead of Java enum:
Custom Serialization
You must use the generated custom Jackson ObjectMapper for serialization/deserialization:
Build Customization
Preserve customizations: Use build-extras.gradle for additions (untouched by generation updates)
Add plugins: Use additionalPlugins property in gen.yaml
Manage dependencies: Add to build-extras.gradle or use additionalDependencies in gen.yaml
Method parameter handling is controlled by the maxMethodParams configuration in gen.yaml:
When parameter count ≤ maxMethodParams:
When parameter count > maxMethodParams or maxMethodParams = 0:
Default Values & Constants
The SDK handles OpenAPI default and const keywords with lazy-loading behavior:
Default Values
Fields with default values use Optional wrappers and lazy-loading:
Important: Default values are lazy-loaded once. If the OpenAPI default is invalid for the field type (e.g., default: abc for type: integer), an IllegalArgumentException is thrown.
Workarounds for invalid defaults:
Regenerate SDK with corrected OpenAPI default
Always set the field explicitly to avoid lazy-loading the invalid default
Constant Values
Fields with const values are read-only and set internally:
Like default values, const values are lazy-loaded once. Invalid const values throw IllegalArgumentException.
User Agent Strings
The Java SDK includes a user agent string in all requests for tracking SDK usage:
SDKVersion: SDK version defined in gen.yaml
GenVersion: Speakeasy generator version
DocVersion: OpenAPI document version
groupId.artifactId: Concatenated from gen.yaml configuration