Version 7.x is here!
We improved the initialization of SDK making it easier to understand the available options.
NowNASaccounts are the default instance for the SDK andABCstructure was moved to apreviousprefixes.
If you have been using this SDK before, you may find the following important changes:
- Marketplace module was moved to Accounts module, same for classes and references.
- Synchronous variants added for all client methods (e.g.
requestPaymentSync).- Flow client for Payment Sessions (create, submit, complete).
- In most cases, IDE can help you determine from where to import, but if you’re still having issues don’t hesitate to open a ticket.
dependencies {
implementation 'com.checkout:checkout-sdk-java:<version>'
}
Once you check out the code from GitHub, the project can be built using Gradle:
gradlew build
# skip tests
gradlew build -x test
The execution of integration tests require the following environment variables set in your system:
CHECKOUT_DEFAULT_PUBLIC_KEY & CHECKOUT_DEFAULT_SECRET_KEYCHECKOUT_DEFAULT_OAUTH_CLIENT_ID & CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRETCHECKOUT_PREVIOUS_PUBLIC_KEY & CHECKOUT_PREVIOUS_SECRET_KEY<dependency>
<groupId>com.checkout</groupId>
<artifactId>checkout-sdk-java</artifactId>
<version>version</version>
</dependency>
This SDK can be used with two different pair of API keys provided by Checkout. However, using different API keys implies using specific API features.
Please find in the table below the types of keys that can be used within this SDK.
| Account System | Public Key (example) | Secret Key (example) |
|---|---|---|
| Default | pk_abcdef123456ghijkl789mnopqr | sk_123456ghijklm7890abcdefxyz |
| Previous | pk_12345678-abcd-efgh-ijkl-mnopqrstuvwx | sk_abcdef12-3456-ghij-klmn-opqrstuvwxyz |
Note: sandbox keys have a sbox_ or test_ identifier, for Default and Previous accounts respectively.
If you don’t have your own API keys, you can sign up for a test account here.
PLEASE NEVER SHARE OR PUBLISH YOUR CHECKOUT CREDENTIALS.
Default keys client instantiation can be done as follows:
import com.checkout.CheckoutApi;
public static void main(String[] args) {
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.publicKey("public_key") // optional, only required for operations related with tokens
.secretKey("secret_key")
.environment(Environment.PRODUCTION) // required
.environmentSubdomain("subdomain") // optional, Merchant-specific DNS name
.executor() // optional for a custom Executor Service
.build();
final PaymentsClient client = checkoutApi.paymentsClient();
final CompletableFuture<RefundResponse> refundPayment = client.refundPayment("payment_id");
}
The SDK supports client credentials OAuth, when initialized as follows:
import com.checkout.CheckoutApi;
public static void main(String[] args) {
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.oAuth()
.clientCredentials("client_id", "client_secret")
// for a specific authorization endpoint
//.clientCredentials(new URI("https://access.sandbox.checkout.com/connect/token"), "client_id", "client_secret")
.scopes(OAuthScope.GATEWAY, OAuthScope.VAULT, OAuthScope.FX)
.environment(Environment.PRODUCTION) // required
.environmentSubdomain("subdomain") // optional, Merchant-specific DNS name
.executor() // optional for a custom Executor Service
.build();
final PaymentsClient client = checkoutApi.paymentsClient();
final CompletableFuture<RefundResponse> refundPayment = client.refundPayment("payment_id");
}
If your pair of keys matches the Previous type, this is how the SDK should be used:
import com.checkout.previous.CheckoutApi;
public static void main(String[] args) {
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.previous()
.staticKeys()
.publicKey("public_key") // optional, only required for operations related with tokens
.secretKey("secret_key")
.environment(Environment.PRODUCTION) // required
.environmentSubdomain("subdomain") // optional, Merchant-specific DNS name
.executor() // optional for a custom Executor Service
.build();
final PaymentsClient client = checkoutApi.paymentsClient();
final CompletableFuture<RefundResponse> refundPayment = client.refundPayment("payment_id");
}
The SDK supports SLF4J as logger provider, you need to provide your configuration file through resources folder.
All the API responses that do not fall in the 2** status codes will cause a CheckoutApiException. The exception encapsulates
the responseHeaders, httpStatusCode and a map of errorDetails, if available.
Request telemetry is enabled by default in the Java SDK. Request latency is included in the telemetry data. Recording the request latency allows Checkout.com to continuously monitor and improve the merchant experience.
Request telemetry can be disabled by opting out during CheckoutSdk builder step:
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.recordTelemetry(false)
.build();
The SDK provides flexibility in how you handle API operations by supporting both asynchronous and synchronous execution modes. You can freely use the async and sync functions of each client, and they will return CompletableFuture
public interface FlowClient {
CompletableFuture<PaymentSessionResponse> requestPaymentSession(PaymentSessionRequest paymentSessionRequest);
CompletableFuture<SubmitPaymentSessionResponse> submitPaymentSessions(
String paymentId,
SubmitPaymentSessionRequest submitPaymentSessionRequest
);
CompletableFuture<PaymentSessionWithPaymentResponse> requestPaymentSessionWithPayment(
PaymentSessionWithPaymentRequest paymentSessionRequest
);
// Synchronous methods
PaymentSessionResponse requestPaymentSessionSync(PaymentSessionRequest paymentSessionRequest);
SubmitPaymentSessionResponse submitPaymentSessionsSync(
String paymentId,
SubmitPaymentSessionRequest submitPaymentSessionRequest
);
PaymentSessionWithPaymentResponse requestPaymentSessionWithPaymentSync(
PaymentSessionWithPaymentRequest paymentSessionRequest
);
}
The default operation mode of the SDK is async, so you can use async and sync functions freely, or combine them.
The SDK can be configured to operate in two different modes:
synchronous(false) or omit the setting (default)Transport.invoke() and Transport.submitFile() methodssynchronous(true)Transport.invokeSync() and Transport.submitFileSync() methodsIn asynchronous mode, the transport layer:
CompletableFuture immediately// Asynchronous mode configuration
final CheckoutApi asyncApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.synchronous(false) // or omit (default)
.executor(Executors.newFixedThreadPool(20)) // Custom thread pool for async operations
.build();
// Usage - returns immediately, HTTP call happens asynchronously
CompletableFuture<PaymentResponse> future = asyncApi.paymentsClient().requestPayment(request);
// Process result when available
future.thenAccept(response -> {
System.out.println("Payment ID: " + response.getId());
});
In synchronous mode, the transport layer:
// Synchronous mode configuration with resilience
final CheckoutApi syncApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.synchronous(true) // Enable synchronous mode
.resilience4jConfiguration(Resilience4jConfiguration.defaultConfiguration())
.executor(Executors.newFixedThreadPool(10)) // Thread pool for wrapping sync calls
.build();
// Usage - HTTP call executes synchronously with resilience patterns
PaymentResponse response = syncApi.paymentsClient().requestPaymentSync(request);
System.out.println("Payment ID: " + response.getId());
// Both modes have identical parameters for the interfaces
PaymentRequest request = PaymentRequest.builder()
.source(cardSource)
.amount(1000L)
.currency(Currency.USD)
.build();
// Asynchronous - fast return, manual error handling
CompletableFuture<PaymentResponse> asyncResult = asyncApi.paymentsClient().requestPayment(request)
.exceptionally(throwable -> {
// Manual retry logic if needed
if (shouldRetry(throwable)) {
return retryPayment(request);
}
throw new CompletionException(throwable);
});
// Synchronous - built-in resilience, automatic retries
PaymentResponse syncResult = syncApi.paymentsClient().requestPaymentSync(request);
// Resilience4j automatically handles retries for transient errors
// Both modes share the same request objects and API interface
In case that you want to use an integrator or mock server, you can specify your own configuration as follows:
final CustomEnvironment environment = CustomEnvironment.builder()
.checkoutApi(create("https://the.base.uri/")) // the uri for all Checkout operations
.oAuthAuthorizationApi(create("https://the.oauth.uri/connect/token")) // the uri used for OAUTH authorization, only required for OAuth operations
.filesApi(create("https://the.files.uri/")) // the uri used for Files operations, only required for Accounts module
.transfersApi(create("https://the.transfers.uri/")) // the uri used for Transfer operations, only required for Transfers module
.balancesApi(create("https://the.balances.uri/")) // the uri used for Balances operations, only required for Balances module
.sandbox(false) // flag to determine if Sanbox or Production configured, default false
.build();
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(environment) // required
.executor(customHttpClient) // optional for a custom Executor Service
.build();
You don’t need to specify all the previous URI’s, only the ones for the modules that you’re going to use.
The SDK allows you to customize the underlying HTTP client and executor configurations to meet your specific needs, such as custom connection pooling, proxy settings, timeout configurations, or multithreaded operations.
You can provide your own HttpClientBuilder to customize HTTP connection properties:
final HttpClientBuilder customHttpClient = HttpClientBuilder.create()
.setProxy(HttpHost.create("https://proxy"))
.setConnectionTimeToLive(3, TimeUnit.SECONDS);
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION) // required
.environmentSubdomain("subdomain") // optional, Merchant-specific DNS name
.httpClientBuilder(customHttpClient) // optional for a custom HttpClient
.build();
You can provide a custom Executor for handling asynchronous operations. By default, the SDK uses ForkJoinPool.commonPool().
final Executor customExecutor = Executors.newSingleThreadExecutor();
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION) // required
.executor(customExecutor) // optional for a custom Executor Service
.build();
For applications requiring high concurrency and performance, you can configure both a custom executor with a larger thread pool and an HTTP client optimized for connection pooling:
// Configure HttpClient for high concurrency
final HttpClientBuilder multithreadedHttpClient = HttpClientBuilder.create()
.setMaxConnTotal(200) // Maximum total connections
.setMaxConnPerRoute(100) // Maximum connections per route/destination
.setConnectionTimeToLive(60, TimeUnit.SECONDS) // Connection time-to-live
.evictIdleConnections(30, TimeUnit.SECONDS); // Evict idle connections after 30 seconds
// Configure a fixed thread pool executor
final Executor multithreadedExecutor = Executors.newFixedThreadPool(100);
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION) // required
.executor(multithreadedExecutor) // custom thread pool for async operations
.httpClientBuilder(multithreadedHttpClient) // custom HTTP client for connection pooling
.build();
Configuration Options Explained:
executor(Executor): Configures the executor service for handling asynchronous operations. Default is ForkJoinPool.commonPool().httpClientBuilder(HttpClientBuilder): Configures the Apache HttpClient settings. Default is HttpClientBuilder.create().Common HttpClient Settings:
setMaxConnTotal(int): Maximum number of total connections across all routessetMaxConnPerRoute(int): Maximum number of connections per specific route/destinationsetConnectionTimeToLive(long, TimeUnit): Connection lifetime before being evicted from the poolevictIdleConnections(long, TimeUnit): Time after which idle connections are evictedsetProxy(HttpHost): Configure HTTP proxy serversetDefaultConnectionConfig(ConnectionConfig): Set default connection configurationsetDefaultSocketConfig(SocketConfig): Set socket configuration including timeoutsThis section provides specific configuration patterns for different application requirements, focusing on rate limiting and HTTP connection pool optimization.
Configure your HTTP connection pool based on your application’s expected throughput and concurrency requirements.
When using custom configurations, monitor these metrics:
The SDK includes built-in resilience patterns using the Resilience4j library to handle transient errors and improve reliability.
The SDK supports three main resilience patterns:
io.github.resilience4j:resilience4j-retry:1.7.1io.github.resilience4j:resilience4j-ratelimiter:1.7.1io.github.resilience4j:resilience4j-circuitbreaker:1.7.1The resilience patterns are applied as decorators around your HTTP requests in the following order:
The retry mechanism can handle various types of transient errors, including:
final Resilience4jConfiguration resilience4jConfig = Resilience4jConfiguration.builder()
.withDefaultRetry()
// Deactivated by default
//.withDefaultRateLimiter()
// Deactivated by default
//.withDefaultCircuitBreaker()
.build();
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.synchronous(true)
.resilience4jConfiguration(resilience4jConfig)
.build();
Default behavior: Asynchronous mode
Resilience4j: NOT applied by default
Transport: Uses async methods (transport.invoke(), transport.submitFile())
Performance: Faster, non-blocking execution but no automatic retries
To enable synchronous mode with Resilience4j patterns, you need to explicitly set:
CheckoutSdk.builder() .synchronous(true)
So the SDK is designed to be async-first by default, which makes sense for most modern applications that prefer non-blocking operations.
// SYNC transport (with Resilience4j)
@Override
public Response invokeSync(...) {
// ... build request ...
final Supplier<Response> callSupplier = () -> performCall(...);
return executeWithResilience4j(callSupplier); // ← Resilience4j applied here
}
// ASYNC transport (no Resilience4j)
@Override
public CompletableFuture<Response> invoke(...) {
return CompletableFuture.supplyAsync(() -> {
// ... build request ...
return performCall(...); // ← Direct call, no Resilience4j
}, executor);
}
final RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(5) // Maximum retry attempts
.waitDuration(Duration.ofMillis(1000)) // Wait time between retries
.retryOnException(throwable ->
throwable instanceof CheckoutApiException && // Retry on API exceptions
((CheckoutApiException) throwable).getHttpStatusCode() >= 500) // Only 5xx errors
.build();
final Resilience4jConfiguration resilience4jConfig = Resilience4jConfiguration.builder()
.withRetry(retryConfig)
.build();
(deactivated by default)
final RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom()
.limitForPeriod(50) // 50 requests
.limitRefreshPeriod(Duration.ofSeconds(1)) // Per second
.timeoutDuration(Duration.ofSeconds(2)) // Wait up to 2 seconds for permission
.build();
final Resilience4jConfiguration resilience4jConfig = Resilience4jConfiguration.builder()
.withRateLimiter(rateLimiterConfig)
.build();
(deactivated by default)
final CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(60) // Open circuit at 60% failure rate
.waitDurationInOpenState(Duration.ofSeconds(60)) // Wait 60 seconds before trying again
.slidingWindowSize(20) // Consider last 20 requests
.minimumNumberOfCalls(10) // Minimum calls before evaluation
.permittedNumberOfCallsInHalfOpenState(5) // Calls allowed in half-open state
.build();
final Resilience4jConfiguration resilience4jConfig = Resilience4jConfiguration.builder()
.withCircuitBreaker(circuitBreakerConfig)
.build();
final Resilience4jConfiguration resilience4jConfig = Resilience4jConfiguration.builder()
.withRetry(RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(500))
.retryOnException(throwable ->
throwable instanceof CheckoutApiException &&
((CheckoutApiException) throwable).getHttpStatusCode() >= 500)
.build())
// Deactivated by default
//.withRateLimiter(RateLimiterConfig.custom()
// .limitForPeriod(100)
// .limitRefreshPeriod(Duration.ofSeconds(1))
// .timeoutDuration(Duration.ofSeconds(5))
// .build())
// Deactivated by default
//.withCircuitBreaker(CircuitBreakerConfig.custom()
// .failureRateThreshold(50)
// .waitDurationInOpenState(Duration.ofSeconds(30))
// .slidingWindowSize(10)
// .build())
.build();
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.synchronous(true)
.resilience4jConfiguration(resilience4jConfig)
.build();
Important Notes:
synchronous(true))Please refer to Code of Conduct