# stripe-java: two client surfaces, one dependency, and a migration you cannot ignore

> The official Stripe Java client still ships two ways to call the API: the StripeClient introduced in v23 and a legacy pattern the maintainers say will be deprecated soon. The library also bundles decisions about Gson, retries, timeouts and DNS caching that teams inherit silently.

**stripe/stripe-java** — Java library for the Stripe API.    

- Repository: https://github.com/stripe/stripe-java
- Website: https://stripe.com
- Stars: 1,012 · Forks: 412
- Language: Java
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/stripe-stripe-java

## Two ways to reach the same API, and only one of them will grow

stripe-java contains two distinct call surfaces. The current one is the `StripeClient` class, added in v23 of the SDK. The older one, described in the README as the legacy pattern, is still usable. The README is direct about what happens next: the legacy pattern "will be marked as deprecated soon", and once that happens, "new API endpoints will only be accessible in the StripeClient".

The README also says there are "no current plans to remove the legacy pattern for existing API endpoints", while immediately adding that "this may change in the future". That is the honest shape of the commitment. Endpoints that exist today keep working through the legacy pattern for now, but the set of endpoints you can reach that way is frozen. Any integration that expects to pick up a new resource later has to be on `StripeClient` from the start, and the migration guide for v23 is the reference the README points at.

A second structural fact is worth knowing before you design around this library. The library is typed and generated from an API description, and the repository root carries `CODEGEN_VERSION` and `OPENAPI_VERSION` files next to `VERSION` and a `CHANGELOG`. In other words, the surface you program against is not hand written. It is regenerated, which is why a new API version arrives as a version bump rather than a code review.

## Adding the dependency: Gradle, Maven, or two hand-copied JARs

The library supports LTS versions of the JDK, and the README lists Java 8, 11, 17, 21 and 25. That range is wide enough that you can probably keep an existing JVM and still use the library.

For Gradle projects, the dependency goes in the build file as a single line:

```groovy
implementation "com.stripe:stripe-java:34.0.0"
```

For Maven projects, the same artifact goes into the POM:

```xml
<dependency>
  <groupId>com.stripe</groupId>
  <artifactId>stripe-java</artifactId>
  <version>34.0.0</version>
</dependency>
```

If you use neither Gradle nor Maven, the README describes a manual path: download the Stripe JAR from Maven Central at version 34.0.0, download Google Gson 2.10.1, and put both on your classpath. The README states that the Stripe JAR "builds and tests with Gson version 2.10.1", and that you should be able to use "any stable version of Gson that is 2.10.1 or newer". The recommendation is to match 2.10.1 where possible.

That second JAR is a real decision. Gson is a general purpose JSON library, and the README does not document shading or relocation of it, so a project that already pulls in a different Gson version has to reconcile them itself. The ProGuard note in the same section says the same thing from the other direction: if you shrink your bytecode, exclude the library entirely.

```
-keep class com.stripe.** { *; }
```

That rule goes into `proguard.cfg`. It exists because the library reflects over generated model classes to serialize requests and parse responses.

## RequestOptions is where the key, the account and the idempotency token live

Every request method accepts an optional `RequestOptions` object. The README ties it to three concrete needs: setting an idempotency key, using Stripe Connect, and passing the secret API key on each method instead of at construction time.

```java
RequestOptions requestOptions = RequestOptions.builder()
    .setApiKey("sk_test_...")
    .setIdempotencyKey("a1b2c3...")
    .setStripeAccount("acct_...")
    .build();

client.v1().customers().list(requestOptions);

client.v1().customers().retrieve("cus_123456789", requestOptions);
```

The `setStripeAccount` call is the Connect piece: it scopes the request to a connected account, which is what a marketplace integration needs when one HTTP request from your server acts on behalf of several sellers. The idempotency key is the other half of that story. The README states plainly that "Idempotency keys are added to requests to guarantee that retries are safe", which matters directly because the library can retry your calls for you, as described next.

Passing the API key per request is worth a second look in multi-tenant code. A single `StripeClient` can then serve several key sets, but the secret travels on every call, and the object is easy to log if you are not careful about what you put in exception messages.

## Retries and timeouts, and the JVM DNS cache that can break both

The library can retry requests that fail from "an intermittent network problem or other knowingly non-deterministic errors". The retry count is set globally on the client builder:

```java
StripeClient client = StripeClient.builder()
        .setMaxNetworkRetries(2)
        .build();
```

The same knob exists per request through `RequestOptions`, so a single flaky call site can be given a different policy from the rest of the process. That matters more than it first appears, because timeouts are also configurable at both levels, and the README's advice on read timeouts is a warning rather than a default: "Please take care to set conservative read timeouts. Some API requests can take some time, and a short timeout increases the likelihood of a problem within our servers." The examples set a connect timeout of 30 seconds and a read timeout of 80 seconds, both in milliseconds.

```java
RequestOptions options = RequestOptions.builder()
    .setConnectTimeout(30 * 1000) // in milliseconds
    .setReadTimeout(80 * 1000)
    .build();
client.v1().customers().create(params, options);
```

There is a third timeout that is not in the library at all, and the README treats it as a real failure mode. The IP address of the Stripe API is not guaranteed to be static, and default JVM configurations often set the DNS cache TTL to forever. If Stripe's address changes, the README says, "your application's requests to Stripe will all fail until the JVM restarts". The fix is to set the JVM's `networkaddress.cache.ttl` property to `60` seconds. Skip that and a DNS change looks like an outage on your side, which is the sort of incident that gets misattributed for hours.

## A first call: creating a customer with the payment method attached

The README's example is the smallest complete shape of a Stripe call in this library. The type-level imports come first:

```java
import com.stripe.StripeClient;
import com.stripe.exception.StripeException;
import com.stripe.model.Customer;
import com.stripe.net.RequestOptions;
```

The client is constructed with a secret key, and the parameters are assembled through a builder rather than a map. That builder is where the typed model earns its keep: `setDescription`, `setEmail` and `setPaymentMethod` are checked at compile time, so a misspelled field fails the build instead of failing silently at the API boundary.

```java
    public static void main(String[] args) {
        StripeClient client = new StripeClient("sk_test_...");
        CustomerCreateParams params =
            CustomerCreateParams
                .builder()
                .setDescription("Example description")
                .setEmail("test@example.com")
                .setPaymentMethod("pm_card_visa")  // obtained via Stripe.js
                .build();

        try {
            Customer customer = client.v1().customers().create(params);
            System.out.println(customer);
```

Two details in that snippet carry production weight. The payment method id is marked as obtained via Stripe.js, meaning the card data is collected in the browser and the identifier, not the number, travels to your server. And the call is wrapped in a `StripeException` catch, which is the library's single checked failure type for API errors.

```java
        } catch (StripeException e) {
            e.printStackTrace();
        }
    }
}
```

The `client.v1()` segment is the versioned service accessor. Every resource family hangs off it, and the version prefix is what makes a future v2 surface possible without breaking the current one.

## Notices on stdout, and how to turn them off

The library prints notices that Stripe sends in the `Stripe-Notice` header. The rule is more specific than a simple on/off switch: notices "are always printed when the SDK runs in an agent environment", and for calls to test accounts or sandboxes they are also printed when the SDK runs outside an agent environment. The suppression flag therefore applies only outside an agent environment.

The variable has to be set before the integration runs. This is a small feature, but it matters for teams that treat stray output on stdout as noise, or that capture process output as structured logs.

The README also opens a section on how to use undocumented parameters and properties, noting that stripe-java "is a typed library and it supports all public properties or parameters" and that Stripe sometimes runs beta features that introduce new properties before they are public. The section is cut off in the documentation available here, so the exact mechanism for reaching those beta parameters is not spelled out in the part that can be read. Plan for beta features as a source of friction rather than assuming a documented escape hatch.

## The release pipeline rewrites the README, so version numbers in docs drift

The repository root carries a `justfile` and a `Makefile` that does the same work. The Makefile opens with a note that it "is deprecated and slated for deletion; prefer using the equivalent `just` commands".

The interesting part is the `update-version` recipe, because it shows how the version number propagates. It writes the `VERSION` file, then rewrites the Maven Central badge in the README, the JAR download URL, the "Current release version" line, the Gradle dependency snippet, the Maven POM snippet, the `VERSION_NAME` entry in `gradle.properties`, and the `VERSION` constant inside `src/main/java/com/stripe/Stripe.java`. Every version string in the documentation you read is generated from a single source file.

Two recipes exist for development work:

```bash
./gradlew spotlessApply
```

```bash
./gradlew test
```

The `just format` recipe runs `./gradlew spotlessApply`, `just test` runs `./gradlew test`, and `just format-check` runs `./gradlew spotlessCheck`. A single test can be run with `just test-one` and a test path, and there is a `just print-version-info` recipe that prints the API version the SDK pins along with the lowest runtime it supports. That is the fastest way to answer two questions that come up during an upgrade review: which API date the library is frozen against, and whether your runtime is still inside the supported range.

The Makefile's own commands are the same Gradle invocations, `codegen-format` and `test`, and the file itself is scheduled for removal, so new contributors are pointed at `just`.

## Where this client is the wrong choice

The clearest boundary is the migration itself. A codebase built on the legacy pattern has a finite amount of runway: the README says the legacy pattern will be marked as deprecated soon and that new endpoints will only be accessible in the StripeClient. Teams that need new API endpoints soon have a scheduled piece of migration work, and the reference for it is the v23 migration guide rather than anything in this library.

Second, the dependency footprint is not trivial for a hand-managed build. Two JARs, a ProGuard exclusion, and a Gson version to keep aligned. Projects that already run a dependency resolution and shrinker have less to think about.

Third, beta parameters are a documented gap. The library is typed, and typed libraries need a matching model class for a field to exist. The README acknowledges that Stripe runs betas with properties ahead of the public surface, and the section explaining how to reach them is incomplete in the available documentation.

Finally, timeouts. The README's guidance is that some API requests are slow, and that a short read timeout increases the chance of a failure inside Stripe's own servers. The library will let you set aggressive timeouts, and doing so with the default of a cautious client means you own the trade-off between latency and error rate. Nothing in the library prevents a fast timeout, and nothing in it warns you at runtime either.

## Conclusion

Adopt stripe-java if you are writing a new Java integration against Stripe on a supported JDK, and start on StripeClient rather than the legacy pattern. Do not adopt it if you need the older static API surface, since the maintainers state new endpoints will only be reachable through StripeClient. Before you commit, check the pinned API version reported by `just print-version-info` against the Stripe account version you use, and decide deliberately whether your build will manage Gson 2.10.1 or newer on its own.

## FAQ

### What Java versions does stripe-java support?

The README says the library supports LTS versions of the JDK and lists Java 8, 11, 17, 21 and 25.

### Do I need to add Gson myself when using stripe-java?

With Gradle or Maven the dependency is pulled transitively, but the manual JAR path in the README asks you to download Google Gson 2.10.1 alongside the Stripe JAR. The library builds and tests with Gson 2.10.1, and any stable version at 2.10.1 or newer is expected to work.

### How do I stop the library from retrying a failing request?

Retries are opt-in, and the retry count is configured with `setMaxNetworkRetries` on the client builder or on `RequestOptions`. The library adds idempotency keys to requests so that retries are safe.

### What should I do if requests to Stripe suddenly start failing in production?

The README points at DNS caching: default JVM configurations can hold a DNS cache TTL of forever, so if the IP address of the Stripe API changes, requests fail until the JVM restarts. The suggested fix is to set the `networkaddress.cache.ttl` property to `60` seconds.

## Sources

- [License: MIT](https://github.com/stripe/stripe-java/blob/master/LICENSE)
- [Project website](https://stripe.com)
- [README](https://github.com/stripe/stripe-java/blob/master/README.md)
- [Releases](https://github.com/stripe/stripe-java/releases)
- [stripe/stripe-java on GitHub](https://github.com/stripe/stripe-java)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/stripe-stripe-java
