Migrating to Java generator v5
Version 5.0.0 of the Java SDK generator renames many generated types, changes pagination and error handling, and generates more precise field types. Release the regenerated SDK as a new major version, and share this page with your SDK users so they can update their code.
The changes below are ordered from most to least likely to affect you. Compile existing code against the regenerated SDK first; the compiler lists most of the changes you need to make.
Before you upgrade
Generator versions 5.0.0 and later read configuration from sdk-config.yml instead of generators.yml. Run fern sdk migrate to create sdk-config.yml from your existing configuration.
Type names and imports change
This change affects every SDK and causes the most compile errors, so fix it first.
Most request wrapper names are unchanged. The idiomatic-request-names option defaults to true, which produces the same verb-first names as the previous generator. Set it to false to use resource-first names. When the API definition doesn’t name a request body, the generated name may leave out the resource name:
Core exception types include the client name:
Packages below the root package move. Per-resource types packages are merged into the resource package, and error classes move to a single errors package at the root.
If you don’t set package-prefix, the root package can change. Set package-prefix to your current root package, such as com.plantstore.api, to keep it.
To fix: Rename types, then remove generated-package imports and let your IDE resolve them again. A cannot find symbol error for a type you used before usually means the type has a different name.
Paginated methods return a pager
List endpoints return an iterable pager that fetches the next page as you iterate, instead of a single page. Async methods return a CompletableFuture that resolves to a pager.
To fix: Replace cursor loops with iteration. Call getResponse() to read the raw response, such as a cursor or total count.
Responses are validated against the schema
Schema constraints such as pattern, minLength, minimum, maximum, minItems, and uniqueItems are enforced when responses are decoded, including collections and paginated items. A response that violates the schema throws an exception that names the invalid field. Request validation remains off by default.
To fix: Run integration tests against the regenerated SDK. If the API returns responses that violate the schema, fix the schema. To unblock yourself in the meantime, set validateResponses: false.
Builders require different fields
Staged builders read required fields from the spec. A field the spec marks as required becomes a required builder stage, even if the previous generator treated it as optional.
To fix: Supply the required fields. If a field isn’t required by the API, mark it as optional in the spec.
Exceptions are organized by HTTP status
The SDK generates the standard set of status-named exceptions even if the spec doesn’t declare them, plus one SDK exception for unmapped statuses. An exception declared for a nonstandard status gets a generic name, and exception messages change.
To fix: Check every catch that names a generated exception, and replace checks on exception message text with checks on the status code.
Dates and integers use specific types
Fields with format: date or format: date-time are LocalDate and OffsetDateTime instead of String. Integer fields use the width of their format, so int64 fields are Long instead of Integer.
To fix: Remove date parsing code, and change Integer to Long where the compiler requires it.
String fields with a declared enum become enums
A string field whose spec declares an enum list is a Java enum instead of a String.
To fix: Compare against enum constants. toString() returns the wire value.
File uploads are set on the request object
On file-upload endpoints, the file is a field on the request object instead of a separate method parameter. The file is a FileStream, which includes its filename and content type.
To fix: Set the file on the request builder, and pass the input stream and filename to FileStream.
label and matrix path parameters are encoded
Path parameters that use the OpenAPI label or matrix style are encoded as the spec defines instead of as plain path segments. The simple style is unchanged.
To fix: Confirm that your API’s routing accepts the encoded form.
Build files and documentation change
The SDK still includes build.gradle and settings.gradle, but doesn’t include the Gradle wrapper or the Spotless plugin. Java compatibility is set in the java {} block, which supports Gradle 8 and 9.
The per-resource documentation/ directory is removed. The SDK root includes README.md, reference.md, which lists every endpoint with its signature and an example, and CONTRIBUTING.md.
Tests for the SDK’s runtime helpers aren’t generated. To generate tests for your API, turn on generateTests. The generated suite includes a serialization round-trip test for each model and a wire test for each endpoint that uses MockWebServer. The build file adds junit-jupiter, junit-platform-launcher, mockwebserver, and a test { useJUnitPlatform() } task.
To fix: Check in your own Gradle wrapper, and update links to documentation/ to point to reference.md. Generated tests don’t cover behavior that depends on your server, so add integration tests that cover response validation and exception handling.
WebSocket channels
The following changes apply to SDKs with WebSocket channels.
Credentials are required to connect
Credentials declared by the channel’s security scheme are sent with the WebSocket handshake. If you don’t pass a credential, it’s read from the scheme’s environment variable. If neither is set, connect() throws before opening the socket. An explicit header takes precedence.
To fix: Set the credential if you open sockets without one.
The two type packages swap contents
Frame models move from the per-channel resources.<channel>.<version>.types package to the shared types package, and parameter enums move the other way.
To fix: Move frame model imports to the shared package and parameter enum imports to the per-channel package. Moving every import to one package fixes half the errors and causes the other half.
The discriminator is a typed field
Frame models declare the type field. getType() still returns a String, as before. What changed: type is no longer also collected into the additional-properties map. Code that reads type from the additional-properties map gets null.
To fix: Nothing, if you already read the discriminator from getType(). Check any code that iterates the additional-properties map expecting type to be there.
Parameter wrapper types become String
Connect parameters that had a wrapper class with an of(...) factory are String. Enum parameters keep their types. The query string sent is unchanged.
To fix: Replace each Wrapper.of(value) call with the value’s string form.
Reconnection reports exhaustion
When the SDK runs out of reconnection attempts, it passes an IllegalStateException to your error handler instead of stopping silently. The same maxRetries value makes one more attempt than before.
To fix: Handle the exhaustion case in your error handler, and reduce maxRetries by one if you depend on the exact number of attempts.
disconnect() before connecting doesn’t throw
Calling disconnect() on a socket that was never connected returns normally instead of throwing a NullPointerException.
To fix: No changes are required. You can remove guards around disconnect().