Generate a Java SDK from OpenAPI / Swagger
Java SDK Overview
Speakeasy's Java SDK is designed to be easy to use and debug. This includes generating strongly typed classes that enforce required fields and other validations to ensure the messages sent are correct. This allows for a tighter development cycle so your API consumers can spend less time developing solutions using your API.
The core features of the SDK include:
- Type-safety - strong-typing used extensively so that problems are seen at compile-time not run-time
- Null-safety - primitive types used where possible improving compile-time null-safety,
java.util.Optional
andJSONNullable
classes used for non-required and nullable fields. Passing Javanull
arguments will provoke an exception. - Builders and method chaining for all SDK objects. For example, to create a
Person
object:
Person person = Person.builder() .firstName("Albert") .lastName("Einstein") .dateOfBirth(LocalDate.parse("1879-03-14")) .build();
- All-field constructors available for nearly all SDK objects (so a user can get compile-time indication of changes to the OpenAPI document if required)
- Readability - appropriately formatted method chaining is more comprehensible and maintainable
- Discoverability - method chaining and favourable naming strategies make life easier (for example to build a
Person
object you callPerson.builder()
, notnew Builders.PersonFactory()
) - Convenient overloads in builders (for example so that a
long
can be passed directly when the underlying field isOptional<Long>
) java.util.Optional
used for non-required argumentsJsonNullable
used for nullable arguments- java platform
OffsetDateTime
andLocalDate
types used fordate-time
anddate
- A
utils
package that provides shared code used by generated classes, making the generated code easier follow - Authentication support for OAuth flows and other standard security mechanisms
- Custom enum types using string or integer values
- Pagination support including the option to return
java.util.Stream
so paging is auto-handled - Well-formatted source code to make debugging easier
The SDK includes minimal dependencies. It requires
- Jackson Library (opens in a new tab) to serialize and deserialize data over the wire.
- Apache Httpclient (opens in a new tab) to make HTTP requests
- Jayway JsonPath (opens in a new tab) to support JSON path expressions in speakeasy metadata fields in OpenAPI documents
Java Package Structure
HTTP Client
The Java SDK HTTP client is completely configurable using a class implementing the following interface (found under the util
package of the generated code):
public interface HTTPClient { public HTTPResponse<byte[]> send(HTTPRequest request) throws IOException, InterruptedException, URISyntaxException}
A default implementation is provided based on java.net.HttpClient
. Any developer using the SDK can replace this implementation their own implementation very easily:
MyHttpClient client = new MyHttpClient();SDK sdkInstance = SDK.builder().setClient(client).build();
This gives them the flexibility to setup proxies, cookie jars, special headers, or any other low-level customization.
Java SDK Data Types & Classes
Primitives, native types
Where possible the Java SDK uses native types from the language. Primitives are used wherever possible for increased null-safety. For example:
java.lang.String
java.time.OffsetDateTime
java.time.LocalDate
java.math.BigInteger
java.math.BigDecimal
int
(orjava.lang.Integer
)long
(orjava.lang.Long
)float
(orjava.lang.Float
)double
(orjava.lang.Double
)boolean
(orjava.lang.Boolean
)- etc...
Unlimited-precision Numerics
Using high precision decimal or integer types is crucial in certain applications such as code manipulating monetary amounts and in situations where overflow, underflow, or truncation caused by precision loss can lead to significant incidents.
To mark a field as an unlimited precision integer you can use:
type: integer format: bigint
or
type: string format: bigint
The above types are mapped to java.math.BigInteger
in the generated SDK and object builders have convenient overloads that allow passing integer values directly without wrapping with BigInteger
.
Similarly, for unlimited precision decimal:
type: number format: decimal
or
type: string format: decimal
The above types are mapped to java.math.BigDecimal
in the generated SDK in the generated SDK and object builders have convenient overloads that allow passing float/double values directly without wrapping with BigDecimal
.
Note: SDKs in other languages may choose to map to native high precision types rather than unlimited precision types. Check the documentation for the language you are interested in.
Union types (oneOf)
Support for polymorphic types is critical to most production applications. In OpenAPI, these types are defined using the oneOf
keyword. Non-discriminated oneOf
types are supported.
Consider this OpenAPI fragment:
Pet: oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog"
Here's how a Pet
is created in java code:
Cat cat = ...;Dog dog = ...;// Pet.of only accepts Cat or Dog types, and throws if passed nullPet pet = Pet.of(cat);
Here's how a Pet
is inspected:
Pet pet = ...; // might be returned from an SDK callif (pet.value() instanceof Cat) { Cat cat = (Cat) pet.value(); // do something with the cat} else if (pet.value() instanceof Dog) { Dog dog = (Dog) pet.value(); // do something with the dog} else { throw new RuntimeException("unexpected value, openapi definition has changed?");}
If developing with Java 14+ then you can make use of pattern matching language features:
Pet pet = ...; // might be returned from an SDK callif (pet.value() instanceof Cat cat) { // do something with the cat} else if (pet.value() instanceof Dog dog) { // do something with the dog} else { throw new RuntimeException("unexpected value, openapi definition has changed?");}
Parameters
If configured, the Java SDK will generate methods with parameters as part of the method call itself rather than as part of a separate request object. This will be done for up to maxMethodParams
parameters, that can be set in the gen.yaml
file.
If the maxMethodParams
configuration option is absent or set to zero, all generated methods require a single request object that contains all the parameters that may be used to call an endpoint in the API.
Default values
The default
keyword in the OpenAPI specification allows a user to omit a field/parameter and it will be set with a given default value. Default values are represented in the Java SDK with java.util.Optional
wrappers. Passing Optional.empty()
(or if using a builder, not setting the field/parameter) will mean that the default value in the OpenAPI document is used. Bear in mind that it is lazy-loaded (only once) and that if the default value is not valid for the given type (say default: abc
is specified for type: integer
) an IllegalArgumentException
will be thrown. If you encounter this situation, you have two options:
- regenerate the SDK with a fixed default value in the OpenAPI document
or
- set the value of the field explicitly (so that the once-only lazy-load of the default value never happens). This technique is the most likely immediate workaround for a user that does not own the SDK repository
Constant values
The const
keyword in the OpenAPI specification ensures that a field is essentially read-only and that its value will be the specified constant. const
fields will not be settable in all-parameter constructors or builders, their value will be set internally. However, const
fields are readable in terms of object getters. const
values are lazy-loaded once-only (like default
values). If the const
value is not valid for the given type then an IllegalArgumentException
will be thrown and the best fix is:
- regenerate the SDK with a fixed
const
value in the OpenAPI document
Errors
To handle errors in the Java SDK, you need to check the status code of the response. If it is an error response, then the error
field of the response will be set the decoded error value.
Coming Soon
Support for throwing non-successful status codes as exceptions coming soon.
Pagination and java.util.Stream
Enabling pagination for an operation in your API is described here.
If pagination is enabled for an operation then when using the operation builder you have the option
to run .call()
or .callAsStream()
.
.call()
will return the first page and you will have to repeatedly check for the existence of another page and retrieve it.callAsStream()
returns ajava.util.Stream
of the pages allowing you to use the convenientjava.util.Stream
API and not be concerned with handling paging yourself.
Below is an example of callAsStream()
.
SDK sdk = SDK.builder() ... ;sdk.searchDocuments() // builder for the request .contains("simple") // parameter .minSize(200) // parameter .maxSize(400) // parameter .callAsStream() // returns Stream<DocumentsPageResponse> .flatMap(x -> x.res() // returns Optional<DocumentsPage> .stream() .flatMap(y -> y.documents().stream())) // we are now dealing with a Stream<Document> .filter(document -> "fiction".equals(document.category()) .limit(200) // no more than 200 documents .map(document -> document.name()) .forEach(System.out::println);
callAsStream
throws when a response page has a status code of >=300. If you desire different behaviour
then use call
to retrieve each page yourself.
User Agent Strings
The Java SDK will include a user agent (opens in a new tab) string in all requests. This can be leveraged for tracking SDK usage amongst broader API usage. The format is as follows:
speakeasy-sdk/java {{SDKVersion}} {{GenVersion}} {{DocVersion}} {{groupId.artifactId}}
Where
SDKVersion
is the version of the SDK, defined ingen.yaml
and releasedGenVersion
is the version of the Speakeasy generatorDocVersion
is the version of the OpenAPI documentgroupId.artifactId
is the concatenation of thegroupId
andartifactId
defined ingen.yaml