Skip to content

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

  1. Always catch CxSdkClientException — Even if you don't expect errors, wrap API calls in try/catch to avoid unhandled runtime exceptions crashing your application.

  2. 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.

  3. Use exponential backoff — When retrying server or network errors, increase the delay between attempts to avoid overwhelming the API.

  4. Log structured error context — Log status code, API error code/message, and response body so Support can troubleshoot effectively.

  5. Capture rate-limit headers in logs and metrics — Record values from responseHeaders (such as Retry-After and X-RateLimit-Remaining) to drive adaptive throttling and alerting.

  6. Add a circuit breaker for high-throughput traffic — Pair retries with a breaker to prevent retry storms during platform incidents.

  7. Inspect apiErrorMessage for validation errors — The API returns detailed messages describing which fields failed validation and why.

  8. 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);
}