# ...
paths:
  /pet:
    # ...
    post:
      #...
      operationId: addPet
      x-speakeasy-retries:
        strategy: backoff
        backoff:
          initialInterval: 500        # 500 milliseconds
          maxInterval: 60000          # 60 seconds
          maxElapsedTime: 3600000     # 5 minutes
          exponent: 1.5
        statusCodes:
          - 5XX
        retryConnectionErrors: true
```

If you re-generate the SDK now, the new retry configuration will be included.

For more information on configuring retries in your SDK, take a look at the [retries documentation](/docs/customize-sdks/retries).

## SDK dependencies

Let's compare dependencies in the two SDKs.

Here are the OpenAPI Generator SDK dependencies in `build.gradle`:

```groovy
implementation 'io.swagger:swagger-annotations:1.6.8'
implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'io.gsonfire:gson-fire:1.9.0'
implementation 'javax.ws.rs:jsr311-api:1.1.1'
implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation group: 'org.apache.oltu.oauth2', name: 'org.apache.oltu.oauth2.client', version: '1.0.2'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.mockito:mockito-core:3.12.4'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
```

Here are the Speakeasy SDK dependencies from `build.gradle`:

```
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.2'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'org.apache.httpcomponents:httpmime:4.5.14'
implementation 'com.jayway.jsonpath:json-path:2.9.0'
implementation 'commons-io:commons-io:2.15.1'
```

The OpenAPI Generator SDK implements more libraries than the Speakeasy SDK, possibly due to the compatibility requirements and limitations of Java 8. Depending on fewer third-party implementations provides the Speakeasy SDK with some advantages:

- **Less maintenance:** Projects with fewer dependencies have a lower maintenance overhead and less versioning to keep track of long term.
- **Reduced risk of dependency-related issues:** Third-party dependencies increase the risk of bugs and security failures that depend on the third-party provider to fix. A security flaw in a third-party dependency makes your application vulnerable.
- **Improved performance:** Code generally works better in standard Java APIs as they have been through rigorous testing and QA cycles before being made available to the public.
- **Easier adoption:** Projects tend to more readily accept SDK builds that rely on fewer third-party dependencies, due to strict policies regarding the use and management of these dependencies.

## Handling non-nullable fields

Let's see how Speakeasy's enhanced null safety and Optional support work on fields in the `Pet` object.

Take a look at the following declaration taken from the `Pet` object in the Speakeasy SDK `org.openapis.openapi.models.components.Pet.java` file:

```java
    @JsonInclude(Include.NON_ABSENT)
    @JsonProperty("status")
    @SpeakeasyMetadata("form:name=status")
    private Optional<? extends Status> status;
```

Note that the `@JsonInclude` annotation indicates it is `NON-ABSENT` and the `Optional` class is used. The status field here is an enum (`Status`) wrapped in the `Optional` class.

Let's examine the `Status` enum object:

```java
public enum Status {
    AVAILABLE("available"),
    PENDING("pending"),
    SOLD("sold");

    @JsonValue
    private final String value;

    private Status(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }
}
```

Let's compare the Speakeasy SDK `status` field declaration to the same field in the OpenAPI Generator SDK. The following declaration is taken from the `org.openapitools.client.model.Pet.java` file:

```java
public enum StatusEnum {
    AVAILABLE("available"),

    PENDING("pending"),

    SOLD("sold");

    private String value;

    StatusEnum(String value) {
      this.value = value;
    }

    public String getValue() {
      return value;
    }

    @Override
    public String toString() {
      return String.valueOf(value);
    }

    public static StatusEnum fromValue(String value) {
      for (StatusEnum b : StatusEnum.values()) {
        if (b.value.equals(value)) {
          return b;
        }
      }
      throw new IllegalArgumentException("Unexpected value '" + value + "'");
    }

    public static class Adapter extends TypeAdapter<StatusEnum> {
      @Override
      public void write(final JsonWriter jsonWriter, final StatusEnum enumeration) throws IOException {
        jsonWriter.value(enumeration.getValue());
      }

      @Override
      public StatusEnum read(final JsonReader jsonReader) throws IOException {
        String value =  jsonReader.nextString();
        return StatusEnum.fromValue(value);
      }
    }

    public static void validateJsonElement(JsonElement jsonElement) throws IOException {
      String value = jsonElement.getAsString();
      StatusEnum.fromValue(value);
    }
  }

  public static final String SERIALIZED_NAME_STATUS = "status";
  @SerializedName(SERIALIZED_NAME_STATUS)
  private StatusEnum status;
```

At first glance, the OpenAPI Generator SDK also uses an enum approach, representing the `status` field as an enum called `StatusEnum` with three possible values: `AVAILABLE`, `PENDING`, and `SOLD`. A lot of code is generated around this field to handle the enum, but the `Pet` object does not indicate that the OpenAPI Generator SDK `status` field is non-nullable at this point.

In contrast, Speakeasy uses a direct approach to non-nullable fields. The Speakeasy SDK also uses a `Status` enum for the `status` field, but it is wrapped in the `Optional` class provided by the Java standard APIs.

Declaring the `status` field as the `Status` type wrapped in the `Optional` class has some benefits to the developer:

- It helps to avoid possible `NullPointerException` errors when accessing a `null` value.
- It provides a modern way for developers to identify the absence of a value using the `isPresent()` method from the `Optional` class API and exposes other usable methods like `orElse()` and `orElseThrow()`.
- It clearly states the intent and use of the code, which helps to reduce bugs in the long run.

Let's see how client validation works by passing a `null` value to the `findPetsByStatus()` method, which expects `Optional<? extends Status> status`. We create the following builder pattern for a new request:

```java
FindPetsByStatusResponse res = sdk.pet().findPetsByStatus(null);

if (res.body().isPresent()) {
    // handle response
}
```

When we execute this bit of code, we get the following exception:

```
java.lang.IllegalArgumentException: status cannot be null
	at org.openapis.openapi.utils.Utils.checkNotNull(Utils.java:469)
	at org.openapis.openapi.models.operations.FindPetsByStatusRequest$Builder.status(FindPetsByStatusRequest.java:108)
	at org.openapis.openapi.Pet.findPetsByStatus(Pet.java:503)
```

The same exception is generated if we remove the `name` field from the `builder` declaration of a new `Pet` object:

```java
Pet req = Pet.builder()
    .id(1)
    .photoUrls(java.util.List.of(
        "https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=1xw:0.74975xh;center,top&resize=1200:*"))
    .category(Category.builder()
        .id(1)
        .name("Dogs")
        .build())
    .tags(java.util.List.of(
        Tag.builder()
                .build()))
    .build();
```

When we execute the above code, we get the exception:

```bash
java.lang.IllegalArgumentException: name cannot be null
 at org.openapis.openapi.utils.Utils.checkNotNull(Utils.java:469)
 at org.openapis.openapi.models.components.Pet.<init>(Pet.java:62)
 at org.openapis.openapi.models.components.Pet$Builder.build(Pet.java:297)
 at org.openapis.openapi.Test.main(Test.java:40)
```

The null check validation generates this exception when the `Pet` object is initiated and certain values are null. If we look at the class constructor in our `Pet.java` model in the Speakeasy SDK:

```java
public Pet(
            @JsonProperty("id") Optional<? extends Long> id,
            @JsonProperty("name") String name,
            @JsonProperty("category") Optional<? extends Category> category,
            @JsonProperty("photoUrls") java.util.List<String> photoUrls,
            @JsonProperty("tags") Optional<? extends java.util.List<Tag>> tags,
            @JsonProperty("status") Optional<? extends Status> status) {
        Utils.checkNotNull(id, "id");
        Utils.checkNotNull(name, "name");
        Utils.checkNotNull(category, "category");
        Utils.checkNotNull(photoUrls, "photoUrls");
        Utils.checkNotNull(tags, "tags");
        Utils.checkNotNull(status, "status");
        this.id = id;
        this.name = name;
        this.category = category;
        this.photoUrls = photoUrls;
        this.tags = tags;
        this.status = status;
    }
```

We can see that the exception is generated in the `Utils.checkNotNull()` method:

```java
public static <T> T checkNotNull(T object, String name) {
    if (object == null) {
        // IAE better than NPE in this use-case (NPE can suggest internal troubles)
        throw new IllegalArgumentException(name + " cannot be null");
    }
    return object;
}
```

Therefore, if we omit the `name` field or pass a `null` value in the `findPetByStatus()` method, an exception is generated by the check null validation because the `name` and `status` fields explicitly set to `null` in this case.

Let's try creating a `Pet` object without a `name` field using the OpenAPI Generator SDK:

```java
PetApi apiInstance = new PetApi(defaultClient);
ArrayList<String> snoopyPhotos = new ArrayList<>();
snoopyPhotos.add("https://Snoopy.some_photo_platform.com");

Pet pet = new Pet(); // Pet | Create a new pet in the store
pet.setPhotoUrls(snoopyPhotos);
try {
    Pet result = apiInstance.addPet(pet);
} catch (ApiException e) {
    //handle exception
}
```

When we execute the above code, we get a mixed result. The following exception is generated:

```
Exception in thread "main" java.lang.IllegalArgumentException: The required field `name` is not found in the JSON string: {"id":9223372036854775807,"photoUrls":["https://Snoopy.some_photo_platform.com"],"tags":[]}
	at org.openapitools.client.model.Pet.validateJsonElement(Pet.java:361)
	at org.openapitools.client.model.Pet$CustomTypeAdapterFactory$1.read(Pet.java:422)
	at org.openapitools.client.model.Pet$CustomTypeAdapterFactory$1.read(Pet.java:412)
	at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:204)
  ....
```

It appears that the `Pet` object was created on the API, but the call failed retrospectively on the client side. The exception was generated by the SDK's validation process, which checks the JSON response received from the API. You can see the created object in the JSON response included in the exception:

```json
{"id":9223372036854775807,"photoUrls":["https://Snoopy.some_photo_platform.com"],"tags":[]}
```

Validation failed because the name was missing from the JSON string. This validation method is not helpful, as it checks the response after the API call rather than before the request is sent. Consequently, an invalid object was created on the API and the client process failed.

Speakeasy's proactive client validation and method of handling non-nullable fields with the use of the `Optional` class is elegant. Code that is easy to read, understand, and use, and that also helps to build null safety is essential for building robust, maintainable SDKs.

## Generated documentation

Both Speakeasy and OpenAPI Generator generate SDK documentation for the generated code.

Each generator creates a README file to help users get started with the SDK. The OpenAPI Generator README outlines the SDK's compatibility with Java, Maven, and Gradle versions and identifies the available API routes. The Speakeasy README file is more complete and documents more examples.

Speakeasy also generates additional documentation in the `docs` directory, including more detailed explanations of the models and operations; examples of creating, updating, and searching objects; error handling; and guidance on handling exceptions specific to the OpenAPI specification file. A handy "Getting Started" section details how to build the SDK.

In general, we found the Speakeasy documentation to be more complete and helpful. We tested many API call examples from the documentation, and conclude that the Speakeasy docs are production-ready.

## Supported Java versions

The Speakeasy-generated SDK supports Java 11+ environments, and the SDK generated by OpenAPI Generator supports Java 8+.

While the OpenAPI SDK supports more codebases including those still using Java 8, the Speakeasy SDK leverages the enhancements provided by Java 11. Java 8 was released in March 2014, and was the most widely used version of Java until version 11 was released in September 2019.

In 2023, New Relic reported that Java 11 is used in around 56% of production applications, while Java 8 is still in use at around 33%. Both versions are important long-term support (LTS) versions.

## Summary

We've seen how easy it is to generate a powerful, idiomatic SDK for Java using Speakeasy.

If you're building an API that developers rely on and would like to publish full-featured Java SDKs that follow best practices, we highly recommend giving the Speakeasy SDK generator a try.

For more customization options for your SDK using the Speakeasy generator, please see the [Speakeasy documentation](/docs/customize-sdks).

Join our Slack community to let us know how we can improve our Java SDK generator or suggest features.