Error Handling¶
The CX Platform Java SDK provides a single, rich exception class —
CxSdkClientException — that captures all the context you need to diagnose
and recover from API errors. This page explains the exception structure, error
categories, retry logic, and recommended handling patterns.
CxSdkClientException Overview¶
All API errors throw CxSdkClientException, which extends RuntimeException.
This means you are not forced to declare checked exceptions, but you should
catch and handle them explicitly in production code.
Exception Properties¶
| Property | Type | Description |
|---|---|---|
statusCode |
int |
HTTP status code returned by the API (e.g., 400, 401, 500) |
responseBody |
String |
Raw response body from the API |
responseHeaders |
Map<String, List<String>> |
All response headers |
errorCategory |
ErrorCategory |
Categorized error type (see below) |
apiErrorCode |
String |
Machine-readable error code from the API |
apiErrorMessage |
String |
Human-readable error description from the API |
retryable |
boolean |
Whether the request can be retried |
ErrorCategory Enum¶
The errorCategory field classifies every error into one of five categories:
| Category | Description | Retryable | Typical Status Codes |
|---|---|---|---|
NETWORK_ERROR |
Connection failure, DNS resolution, timeout | Yes | N/A (no HTTP response) |
AUTH_ERROR |
Invalid or expired API key, insufficient permissions | No | 401, 403 |
VALIDATION_ERROR |
Malformed request, missing required fields, invalid values | No | 400, 422 |
SERVER_ERROR |
Internal server error, service unavailable | Yes | 500, 502, 503, 504 |
UNKNOWN |
Unclassified error | No | Any other |
Retry Logic¶
The SDK classifies errors into retryable and non-retryable categories:
| HTTP Status | Category | Retryable | Recommended Action |
|---|---|---|---|
| 5xx | SERVER_ERROR |
✅ Yes | Retry with exponential backoff |
| 401 | AUTH_ERROR |
❌ No | Check API key validity |
| 403 | AUTH_ERROR |
❌ No | Verify permissions / scopes |
| 400, 422 | VALIDATION_ERROR |
❌ No | Fix the request payload |
| 404 | VALIDATION_ERROR |
❌ No | Verify resource ID exists |
| Network failure | NETWORK_ERROR |
✅ Yes | Retry with exponential backoff |
Tip
Use the isRetryable() method to programmatically decide whether to retry:
if (e.isRetryable()) {
// implement retry with backoff
}
Retry Budgets and Maximum Retry Counts¶
Use a retry budget to cap how much additional traffic your integration is allowed to generate during downstream failures. This prevents retry storms, protects rate limits, and keeps one failing dependency from consuming all of your worker capacity.
Recommended starting guidance:
| Operation type | Examples | Retry guidance | Recommended max retries |
|---|---|---|---|
| Safe reads / lookup operations | getCustomerById, getServiceLocation, list/read endpoints |
Retry only transient 5xx, 429, and NETWORK_ERROR failures with backoff and jitter |
2-3 |
| Idempotent updates with controlled duplicate risk | setServiceLocation, configuration-style updates |
Retry only if your integration can safely tolerate a repeated request | 1-2 |
| Create / action operations with business side effects | setCustomer, createPaymentRequest, createQuote, scheduling or payment-related actions |
Do not blindly retry unless you have an idempotency or duplicate-detection strategy in your application | 0-1 |
| Authentication / validation failures | 401, 403, 400, 422 responses |
Fix credentials, scope, or payload first | 0 |
Note
Prefer small retry budgets per request path. For most integrations, more than 2-3 retries per operation increases load faster than it improves success rates.
Tip
Combine retry budgets with request timeouts, rate-limit header handling, and circuit breakers. For example, you might allow at most 20% of total outbound calls in a rolling window to be retries.
Catching and Inspecting Errors¶
Basic Pattern¶
import com.gp.cx.sdk.exceptions.CxSdkClientException;
import com.gp.cx.sdk.model.CustomerDTO;
public class BasicErrorHandlingExample {
public static void run(MyClient client) {
try {
CustomerDTO customer = client.getCustomersApi().getCustomerById("customer-uuid");
} catch (CxSdkClientException e) {
System.err.println("API call failed:");
System.err.println(" Status code: " + e.getStatusCode());
System.err.println(" Error category: " + e.getErrorCategory());
System.err.println(" API error code: " + e.getApiErrorCode());
System.err.println(" API message: " + e.getApiErrorMessage());
System.err.println(" Retryable: " + e.isRetryable());
System.err.println(" Response body: " + e.getResponseBody());
}
}
}
interface MyClient {
com.gp.cx.sdk.api.CustomersApi getCustomersApi();
}
Example of Handling by Error Category along with Retry Helper¶
How to utilize retry functionality is included in below example with retryWithBackoff(...) helper method.
import com.gp.cx.sdk.exceptions.CxSdkClientException;
import com.gp.cx.sdk.exceptions.CxSdkClientException.ErrorCategory;
import com.gp.cx.sdk.model.CustomerDTO;
public class CategoryHandlingExample {
public static void run(MyClient client, CustomerDTO customerDTO, Logger log) {
try {
CustomerDTO customer = client.getCustomersApi().setCustomer(customerDTO);
} catch (CxSdkClientException e) {
switch (e.getErrorCategory()) {
case AUTH_ERROR:
log.error("Authentication failed. Verify your API key.");
throw e;
case VALIDATION_ERROR:
log.warn("Validation error: {} - {}", e.getApiErrorCode(), e.getApiErrorMessage());
throw e;
case SERVER_ERROR:
log.warn("Server error ({}). Retrying...", e.getStatusCode());
retryWithBackoff(() -> client.getCustomersApi().setCustomer(customerDTO));
break;
case NETWORK_ERROR:
log.warn("Network error: {}. Retrying...", e.getMessage());
retryWithBackoff(() -> client.getCustomersApi().setCustomer(customerDTO));
break;
default:
log.error("Unexpected error: {}", e.getMessage(), e);
throw e;
}
}
}
public static <T> T retryWithBackoff(java.util.function.Supplier<T> action) {
int maxRetries = 3;
long delayMs = 1000;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return action.get();
} catch (CxSdkClientException e) {
if (!e.isRetryable() || attempt == maxRetries) {
throw e;
}
try {
Thread.sleep(delayMs * attempt);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw e;
}
}
}
throw new IllegalStateException("Retry logic should not reach here");
}
}
interface MyClient {
com.gp.cx.sdk.api.CustomersApi getCustomersApi();
}
interface Logger {
void error(String msg);
void error(String msg, Object a, Throwable t);
void warn(String msg, Object a);
void warn(String msg, Object a, Object b);
}
Logging Configuration Examples¶
The SDK uses SLF4J as its logging facade. To see SDK-level debug output
during development and troubleshooting, configure your SLF4J binding to enable
DEBUG logging for the com.gp.cx.sdk package.
SLF4J + Logback¶
<configuration>
<logger name="com.gp.cx.sdk" level="DEBUG" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
SLF4J + Log4j2¶
<Configuration>
<Loggers>
<Logger name="com.gp.cx.sdk" level="DEBUG" />
<Root level="INFO">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
Warning
Debug logging can include sensitive request and response content. Enable it only in development or controlled troubleshooting environments.
Circuit-Breaker Patterns for High-Throughput Integrations¶
For high-throughput workloads, retries alone can amplify outages. Combine bounded retries with a circuit breaker so repeated transient failures trigger fast-fail behavior for a short window.
import com.gp.cx.sdk.api.CustomersApi;
import com.gp.cx.sdk.exceptions.CxSdkClientException;
import com.gp.cx.sdk.model.CustomerDTO;
public class CircuitBreakerPatternExample {
public static CustomerDTO createCustomer(
CustomersApi customersApi,
CustomerDTO payload,
CircuitBreaker breaker) {
return breaker.executeSupplier(() -> retryWithBackoff(() -> customersApi.setCustomer(payload), 3));
}
private static <T> T retryWithBackoff(java.util.function.Supplier<T> action, int maxRetries) {
long baseDelayMs = 500L;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return action.get();
} catch (CxSdkClientException e) {
boolean rateLimited = e.getStatusCode() == 429;
boolean serverError = e.getStatusCode() >= 500 && e.getStatusCode() <= 599;
boolean networkError = e.getErrorCategory() == CxSdkClientException.ErrorCategory.NETWORK_ERROR;
if ((!rateLimited && !serverError && !networkError) || attempt == maxRetries) {
throw e;
}
sleep(baseDelayMs * attempt);
}
}
throw new IllegalStateException("Retry logic should not reach here");
}
private static void sleep(long delayMs) {
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
interface CircuitBreaker {
<T> T executeSupplier(java.util.function.Supplier<T> supplier);
}
}
If you use a resilience library (such as Resilience4j), configure your circuit
breaker to record 429, 5xx, and NETWORK_ERROR-equivalent failures, and
prefer short open-state durations to protect upstream systems while recovering.
Common Error Scenarios¶
| Scenario | Status | Category | Cause | Solution |
|---|---|---|---|---|
| Invalid API key | 401 | AUTH_ERROR |
API key is missing, malformed, or revoked | Verify the key in your environment config |
| Insufficient permissions | 403 | AUTH_ERROR |
API key lacks access to the requested resource | Check key scopes and permissions |
| Resource not found | 404 | VALIDATION_ERROR |
The ID does not match any existing entity | Verify the resource ID; it may have been deleted |
| Bad request | 400 | VALIDATION_ERROR |
Required fields missing or invalid values | Inspect apiErrorMessage for field-level details |
| Internal server error | 500 | SERVER_ERROR |
Unexpected error on the CX Platform side | Retry; if persistent, contact support with status and error details |
| Service unavailable | 503 | SERVER_ERROR |
Platform is temporarily unavailable | Retry with exponential backoff |
| Connection timeout | N/A | NETWORK_ERROR |
Network unreachable or DNS failure | Check connectivity; retry |
Support Context for Troubleshooting¶
When contacting CX Platform support, include the available error context from
CxSdkClientException (status code, API error code/message, and response body), along with any relevant identifiers (location, customer, etc.).
public class SupportContextExample {
public static void run(com.gp.cx.sdk.api.CustomersApi customersApi, Logger log) {
try {
customersApi.getCustomerById("customer-uuid");
} catch (CxSdkClientException e) {
log.error("Request failed with status {}: {} (code={})",
e.getStatusCode(), e.getApiErrorMessage(), e.getApiErrorCode());
}
}
interface Logger {
void error(String msg, Object a, Object b, Object c);
}
}
Best Practices¶
-
Always catch
CxSdkClientException— Even if you don't expect errors, wrap API calls in try/catch to avoid unhandled runtime exceptions crashing your application. -
Check
isRetryable()before retrying — Only retry errors the SDK marks as retryable. Retrying auth or validation errors wastes resources and will never succeed without changing the request. -
Use exponential backoff — When retrying server or network errors, increase the delay between attempts to avoid overwhelming the API.
-
Log structured error context — Log status code, API error code/message, and response body so Support can troubleshoot effectively.
-
Capture rate-limit headers in logs and metrics — Record values from
responseHeaders(such asRetry-AfterandX-RateLimit-Remaining) to drive adaptive throttling and alerting. -
Add a circuit breaker for high-throughput traffic — Pair retries with a breaker to prevent retry storms during platform incidents.
-
Inspect
apiErrorMessagefor validation errors — The API returns detailed messages describing which fields failed validation and why. -
Enable SLF4J debug logging during development — Configure your logging framework to show SDK-level debug output for full request/response visibility.
Do Not Swallow Exceptions
Avoid empty catch blocks. At minimum, log the exception with status and API error details so issues can be diagnosed later.
// ❌ Bad — silently swallows the error
try { customersApi.setCustomer(dto); } catch (CxSdkClientException e) { }
// ✅ Good — logs the error with context
try {
customersApi.setCustomer(dto);
} catch (CxSdkClientException e) {
log.error("Failed to create customer. status={}, code={}, message={}",
e.getStatusCode(), e.getApiErrorCode(), e.getApiErrorMessage(), e);
}