# Java SDK best practices Recommended practices for configuring, running, and maintaining the Java SDK. ## Keep the SDK up to date Always use a recent version of the SDK to ensure you have the latest features, performance improvements, and security fixes. To stay informed about new releases: - Subscribe to the [GitHub releases feed](https://github.com/commercetools/commercetools-sdk-java-v2/releases.atom) - Watch the [GitHub repository](https://github.com/commercetools/commercetools-sdk-java-v2) ## Store credentials securely Never hardcode credentials such as `clientId` and `clientSecret` in your source code. Always load them from environment variables or a secrets management service at runtime. ```java title="Load credentials from environment variables" String clientId = System.getenv("CTP_CLIENT_ID"); String clientSecret = System.getenv("CTP_CLIENT_SECRET"); String projectKey = System.getenv("CTP_PROJECT_KEY"); ProjectApiRoot apiRoot = ApiRootBuilder.of() .defaultClient( ClientCredentials.of() .withClientId(clientId) .withClientSecret(clientSecret) .build(), ServiceRegion.GCP_EUROPE_WEST1 ) .build(projectKey); ``` ## Reuse the API client The `ProjectApiRoot` (and equivalent root objects for other APIs) is thread-safe and designed to be created once and reused across your application. Creating a new client per request is wasteful. Each client holds its own thread pool and connection pool. Create a single instance and inject or share it through your application: ```java title="Create the API client once at application startup" ProjectApiRoot apiRoot = ApiRootBuilder.of() .defaultClient( ClientCredentials.of() .withClientId(System.getenv("CTP_CLIENT_ID")) .withClientSecret(System.getenv("CTP_CLIENT_SECRET")) .build(), ServiceRegion.GCP_EUROPE_WEST1 ) .build(System.getenv("CTP_PROJECT_KEY")); // Reuse apiRoot throughout the application lifetime ``` For Spring applications, declare the client as a `@Bean` so the framework manages its lifecycle. ## Close the client The Java SDK client holds resources including thread pools and IO connections. The `ApiRoot` classes implement the `Closeable` interface and must be closed when your application shuts down. ### Explicit close Call `close()` explicitly when your application shuts down: ```java title="Close the API client" // Close the client to release thread pools and IO connections apiRoot.getApiHttpClient().close(); ``` For Spring, use `@PreDestroy` or register a shutdown hook: ```java title="Close the API client with @PreDestroy" @PreDestroy public void closeClient() { apiRoot.getApiHttpClient().close(); } ``` ### try-with-resources For short-lived operations (such as scripts or batch jobs), use a try-with-resources block to automatically close the client: ```java title="Close the API client with try-with-resources" try (ProjectApiRoot apiRoot = ApiRootBuilder.of() .defaultClient( ClientCredentials.of() .withClientId(System.getenv("CTP_CLIENT_ID")) .withClientSecret(System.getenv("CTP_CLIENT_SECRET")) .build(), ServiceRegion.GCP_EUROPE_WEST1 ) .build(System.getenv("CTP_PROJECT_KEY"))) { // Perform operations Project project = apiRoot.get().executeBlocking().getBody(); System.out.println(project.getKey()); } // client is automatically closed here ``` ## Troubleshooting ### ApiHttpResponse.getMessage() returns HTTP status text `ApiHttpResponse` is an abstraction over the raw HTTP transfer. Its `getMessage()` method returns the HTTP status reason phrase (such as `OK`, `Bad Request`, or `Not Found`), not the response body. When a request fails, the [ErrorMiddleware](/dev-tooling/java-sdk-middleware.md#errormiddleware) throws an exception whose message contains the HTTP body. The thrown exceptions extend [ApiHttpException](https://commercetools.github.io/commercetools-sdk-java-v2/javadoc/io/vrap/rmf/base/client/ApiHttpException.html), which provides a `getBodyAs()` helper to deserialize the error body: ```java title="Handle API errors" apiRoot.products().post(draft).execute() .exceptionally(throwable -> { if (throwable instanceof BadRequestException) { // BadRequestException and ConcurrentModificationException automatically // deserialize the error response String message = ((BadRequestException) throwable) .getErrorResponse() .getMessage(); } if (throwable instanceof ApiHttpException) { // For all other API errors, use getBodyAs() to deserialize manually String message = ((ApiHttpException) throwable) .getBodyAs(ErrorResponse.class) .getMessage(); } return null; }); ``` ### Further resources - [Middleware reference](/dev-tooling/java-sdk-middleware.md): configure error handling, retry, logging, and more - [Customization guide](/dev-tooling/java-sdk-customization.md): configure proxies, timeouts, HTTP client, and authentication flows - [Integration tests](https://github.com/commercetools/commercetools-sdk-java-v2/tree/main/commercetools/commercetools-sdk-java-api/src/integrationTest): working examples for common SDK operations ## Related pages - [Area overview page with navigation](/dev-tooling.md) - [Previous page: Customization](/dev-tooling/java-sdk-customization.md) - [Next page: Query Predicate builders](/dev-tooling/java-sdk-predicates.md)