CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-anthropic--anthropic-java

The Anthropic Java SDK provides convenient access to the Anthropic REST API from applications written in Java

Overview
Eval results
Files

errors.mddocs/

Error Handling

The Anthropic Java SDK provides a comprehensive exception hierarchy for handling different types of errors that may occur during API interactions. All exceptions are unchecked (extending RuntimeException) to provide flexibility in error handling without forcing verbose exception declarations.

Exception Hierarchy

All Anthropic SDK exceptions extend from the base class:

package com.anthropic.errors;

public class AnthropicException extends RuntimeException {
    public AnthropicException(String message);
    public AnthropicException(String message, Throwable cause);
}

The exception hierarchy is organized into several categories:

AnthropicException (base)
├── AnthropicServiceException (HTTP errors)
│   ├── BadRequestException (400)
│   ├── UnauthorizedException (401)
│   ├── PermissionDeniedException (403)
│   ├── NotFoundException (404)
│   ├── UnprocessableEntityException (422)
│   ├── RateLimitException (429)
│   ├── InternalServerException (5xx)
│   ├── UnexpectedStatusCodeException (other status codes)
│   └── SseException (streaming errors)
├── AnthropicIoException (network/I/O errors)
├── AnthropicRetryableException (retryable failures)
└── AnthropicInvalidDataException (parsing/validation errors)

Service Exceptions

Service exceptions represent HTTP errors returned by the Anthropic API. All service exceptions extend AnthropicServiceException:

package com.anthropic.errors;

import com.anthropic.core.http.Headers;

public class AnthropicServiceException extends AnthropicException {
    private final int statusCode;
    private final Headers headers;

    public int statusCode();
    public Headers headers();
}

BadRequestException (400)

Indicates invalid request parameters or malformed request data:

package com.anthropic.errors;

public class BadRequestException extends AnthropicServiceException {
    public BadRequestException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Invalid parameter values
  • Malformed JSON in request body
  • Missing required parameters
  • Parameters that conflict with each other

Example:

import com.anthropic.errors.BadRequestException;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;

try {
    MessageCreateParams params = MessageCreateParams.builder()
        .maxTokens(0L)  // Invalid: must be positive
        .addUserMessage("Hello")
        .model(Model.CLAUDE_SONNET_4_20250514)
        .build();

    client.messages().create(params);
} catch (BadRequestException e) {
    System.err.println("Bad request: " + e.getMessage());
    System.err.println("Status code: " + e.statusCode());
}

UnauthorizedException (401)

Indicates authentication failure:

package com.anthropic.errors;

public class UnauthorizedException extends AnthropicServiceException {
    public UnauthorizedException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Missing API key
  • Invalid API key
  • Expired authentication token

Example:

import com.anthropic.errors.UnauthorizedException;

try {
    Message message = client.messages().create(params);
} catch (UnauthorizedException e) {
    System.err.println("Authentication failed: " + e.getMessage());
    // Log for debugging, check API key configuration
}

PermissionDeniedException (403)

Indicates insufficient permissions for the requested operation:

package com.anthropic.errors;

public class PermissionDeniedException extends AnthropicServiceException {
    public PermissionDeniedException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • API key lacks necessary permissions
  • Access to specific model or feature denied
  • Organization or account restrictions

Example:

import com.anthropic.errors.PermissionDeniedException;

try {
    Message message = client.messages().create(params);
} catch (PermissionDeniedException e) {
    System.err.println("Permission denied: " + e.getMessage());
    // Check account permissions and model access
}

NotFoundException (404)

Indicates the requested resource does not exist:

package com.anthropic.errors;

public class NotFoundException extends AnthropicServiceException {
    public NotFoundException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Invalid resource ID
  • Resource has been deleted
  • Incorrect endpoint path

Example:

import com.anthropic.errors.NotFoundException;

try {
    MessageBatch batch = client.messages().batches().retrieve("invalid-id");
} catch (NotFoundException e) {
    System.err.println("Resource not found: " + e.getMessage());
    // Handle missing resource gracefully
}

UnprocessableEntityException (422)

Indicates the request was well-formed but contains semantic errors:

package com.anthropic.errors;

public class UnprocessableEntityException extends AnthropicServiceException {
    public UnprocessableEntityException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Invalid tool definitions
  • Malformed JSON schema
  • Invalid model parameters
  • Business logic validation failures

Example:

import com.anthropic.errors.UnprocessableEntityException;

try {
    Message message = client.messages().create(params);
} catch (UnprocessableEntityException e) {
    System.err.println("Unprocessable entity: " + e.getMessage());
    // Review request parameters and tool definitions
}

RateLimitException (429)

Indicates rate limit has been exceeded:

package com.anthropic.errors;

public class RateLimitException extends AnthropicServiceException {
    public RateLimitException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Too many requests in a time window
  • Token usage limits exceeded
  • Concurrent request limits exceeded

Example:

import com.anthropic.errors.RateLimitException;
import java.util.Optional;

try {
    Message message = client.messages().create(params);
} catch (RateLimitException e) {
    System.err.println("Rate limit exceeded: " + e.getMessage());

    // Check for retry-after header
    Optional<String> retryAfter = e.headers().get("retry-after").stream().findFirst();
    if (retryAfter.isPresent()) {
        int seconds = Integer.parseInt(retryAfter.get());
        System.out.println("Retry after " + seconds + " seconds");
    }
}

InternalServerException (5xx)

Indicates a server-side error:

package com.anthropic.errors;

public class InternalServerException extends AnthropicServiceException {
    public InternalServerException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Temporary server issues
  • Service overload
  • Internal server errors

Example:

import com.anthropic.errors.InternalServerException;

try {
    Message message = client.messages().create(params);
} catch (InternalServerException e) {
    System.err.println("Server error: " + e.getMessage());
    System.err.println("Status code: " + e.statusCode());
    // Implement retry logic with exponential backoff
}

UnexpectedStatusCodeException

Handles HTTP status codes not covered by specific exception types:

package com.anthropic.errors;

public class UnexpectedStatusCodeException extends AnthropicServiceException {
    public UnexpectedStatusCodeException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Example:

import com.anthropic.errors.UnexpectedStatusCodeException;

try {
    Message message = client.messages().create(params);
} catch (UnexpectedStatusCodeException e) {
    System.err.println("Unexpected status code: " + e.statusCode());
    System.err.println("Message: " + e.getMessage());
}

Streaming Exceptions

SseException

Thrown for errors that occur during Server-Sent Events (SSE) streaming after a successful initial HTTP response:

package com.anthropic.errors;

public class SseException extends AnthropicServiceException {
    public SseException(
        int statusCode,
        Headers headers,
        String message,
        Throwable cause
    );
}

Common causes:

  • Connection interruption during streaming
  • Malformed SSE data
  • Server-side streaming errors
  • Network issues mid-stream

Example:

import com.anthropic.core.http.StreamResponse;
import com.anthropic.errors.SseException;
import com.anthropic.models.messages.RawMessageStreamEvent;

try (StreamResponse<RawMessageStreamEvent> stream =
        client.messages().createStreaming(params)) {

    stream.stream().forEach(event -> {
        // Process streaming events
        System.out.println(event);
    });
} catch (SseException e) {
    System.err.println("Streaming error: " + e.getMessage());
    // Handle interrupted stream, may need to retry
}

I/O Exceptions

AnthropicIoException

Represents network and I/O errors during communication with the API:

package com.anthropic.errors;

public class AnthropicIoException extends AnthropicException {
    public AnthropicIoException(String message);
    public AnthropicIoException(String message, Throwable cause);
}

Common causes:

  • Network connectivity problems
  • DNS resolution failures
  • Connection timeouts
  • SSL/TLS handshake errors
  • Socket errors

Example:

import com.anthropic.errors.AnthropicIoException;

try {
    Message message = client.messages().create(params);
} catch (AnthropicIoException e) {
    System.err.println("I/O error: " + e.getMessage());
    // Check network connectivity
    // Verify proxy settings
    // Consider retry with backoff
}

Retryable Exceptions

AnthropicRetryableException

Indicates a failure that could be retried by the client:

package com.anthropic.errors;

public class AnthropicRetryableException extends AnthropicException {
    public AnthropicRetryableException(String message);
    public AnthropicRetryableException(String message, Throwable cause);
}

When thrown:

  • Generic retryable failures
  • Transient errors not covered by specific exception types

Example:

import com.anthropic.errors.AnthropicRetryableException;

try {
    Message message = client.messages().create(params);
} catch (AnthropicRetryableException e) {
    System.err.println("Retryable error: " + e.getMessage());
    // Implement custom retry logic
}

Invalid Data Exceptions

AnthropicInvalidDataException

Thrown when successfully parsed data cannot be interpreted correctly:

package com.anthropic.errors;

public class AnthropicInvalidDataException extends AnthropicException {
    public AnthropicInvalidDataException(String message);
    public AnthropicInvalidDataException(String message, Throwable cause);
}

Common causes:

  • Missing required properties in API response
  • Unexpected data types in response
  • Invalid enum values
  • Schema validation failures
  • Response structure doesn't match SDK expectations

Example:

import com.anthropic.errors.AnthropicInvalidDataException;

try {
    Message message = client.messages().create(params);

    // Accessing required property that API unexpectedly omitted
    String id = message.id();
} catch (AnthropicInvalidDataException e) {
    System.err.println("Invalid data: " + e.getMessage());
    // API returned unexpected data format
    // May need SDK update
}

Error Properties

All service exceptions provide access to response metadata:

Status Code

import com.anthropic.errors.AnthropicServiceException;

try {
    client.messages().create(params);
} catch (AnthropicServiceException e) {
    int statusCode = e.statusCode();
    System.err.println("HTTP status: " + statusCode);
}

Headers

Access response headers for additional error context:

import com.anthropic.core.http.Headers;
import com.anthropic.errors.AnthropicServiceException;

try {
    client.messages().create(params);
} catch (AnthropicServiceException e) {
    Headers headers = e.headers();

    // Get specific header
    headers.get("x-error-type").stream()
        .findFirst()
        .ifPresent(type -> System.err.println("Error type: " + type));

    // Iterate all headers
    headers.names().forEach(name -> {
        headers.get(name).forEach(value -> {
            System.err.println(name + ": " + value);
        });
    });
}

Request IDs

Request IDs are crucial for debugging. They can be obtained from raw responses or error headers:

From Raw Responses

import com.anthropic.core.http.HttpResponseFor;
import com.anthropic.models.messages.Message;
import java.util.Optional;

HttpResponseFor<Message> response = client.messages()
    .withRawResponse()
    .create(params);

Optional<String> requestId = response.requestId();
requestId.ifPresent(id ->
    System.out.println("Request ID: " + id)
);

From Exception Headers

import com.anthropic.errors.AnthropicServiceException;
import java.util.Optional;

try {
    client.messages().create(params);
} catch (AnthropicServiceException e) {
    Optional<String> requestId = e.headers()
        .get("request-id")
        .stream()
        .findFirst();

    if (requestId.isPresent()) {
        System.err.println("Request ID for debugging: " + requestId.get());
        // Include this in bug reports to Anthropic
    }
}

Retry Behavior

The SDK automatically retries certain errors with exponential backoff:

Automatically Retried Errors

The following error types trigger automatic retries (default: 2 retries):

  • Connection errors - Network connectivity problems
  • 408 Request Timeout - Server request timeout
  • 409 Conflict - Resource conflict
  • 429 Rate Limit - Rate limit exceeded
  • 5xx Internal Server - Server-side errors

Configuring Retries

Set custom retry behavior at the client level:

import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;

AnthropicClient client = AnthropicOkHttpClient.builder()
    .fromEnv()
    .maxRetries(4)  // Increase from default 2
    .build();

Disable retries:

AnthropicClient client = AnthropicOkHttpClient.builder()
    .fromEnv()
    .maxRetries(0)  // Disable automatic retries
    .build();

Non-Retried Errors

These errors are NOT automatically retried:

  • 400 Bad Request - Fix request parameters
  • 401 Unauthorized - Fix authentication
  • 403 Permission Denied - Fix permissions
  • 404 Not Found - Resource doesn't exist
  • 422 Unprocessable Entity - Fix request semantics

Error Handling Patterns

Basic Try-Catch

import com.anthropic.errors.AnthropicException;
import com.anthropic.models.messages.Message;

try {
    Message message = client.messages().create(params);
    System.out.println("Success: " + message.id());
} catch (AnthropicException e) {
    System.err.println("Error: " + e.getMessage());
    e.printStackTrace();
}

Specific Exception Handling

import com.anthropic.errors.*;

try {
    Message message = client.messages().create(params);
    processMessage(message);

} catch (UnauthorizedException e) {
    System.err.println("Authentication failed. Check API key.");
    // Notify administrator

} catch (RateLimitException e) {
    System.err.println("Rate limit exceeded. Backing off...");
    // Implement backoff strategy

} catch (InternalServerException e) {
    System.err.println("Server error. Will retry.");
    // Implement retry logic

} catch (AnthropicIoException e) {
    System.err.println("Network error: " + e.getMessage());
    // Check connectivity

} catch (AnthropicException e) {
    System.err.println("Unexpected error: " + e.getMessage());
    // Log for investigation
}

Async Error Handling

import com.anthropic.errors.AnthropicException;
import java.util.concurrent.CompletableFuture;

CompletableFuture<Message> future = client.async().messages().create(params);

future.handle((message, error) -> {
    if (error != null) {
        if (error instanceof RateLimitException) {
            System.err.println("Rate limited");
        } else if (error instanceof AnthropicIoException) {
            System.err.println("Network error");
        } else {
            System.err.println("Error: " + error.getMessage());
        }
        return null;
    }

    // Process successful message
    System.out.println("Success: " + message.id());
    return message;
});

Streaming Error Handling

import com.anthropic.core.http.StreamResponse;
import com.anthropic.errors.SseException;
import com.anthropic.models.messages.RawMessageStreamEvent;

try (StreamResponse<RawMessageStreamEvent> stream =
        client.messages().createStreaming(params)) {

    stream.stream().forEach(event -> {
        try {
            processEvent(event);
        } catch (Exception e) {
            System.err.println("Error processing event: " + e.getMessage());
            // Continue processing other events
        }
    });

} catch (SseException e) {
    System.err.println("Streaming interrupted: " + e.getMessage());
    // Attempt to resume or restart stream

} catch (AnthropicException e) {
    System.err.println("Error during streaming: " + e.getMessage());
}

Retry with Exponential Backoff

import com.anthropic.errors.*;
import com.anthropic.models.messages.Message;
import java.time.Duration;

public Message createWithRetry(MessageCreateParams params, int maxAttempts) {
    int attempt = 0;
    long backoffMs = 1000; // Start with 1 second

    while (attempt < maxAttempts) {
        try {
            return client.messages().create(params);

        } catch (RateLimitException e) {
            attempt++;
            if (attempt >= maxAttempts) {
                throw e;
            }

            System.err.println("Rate limited, retrying in " + backoffMs + "ms");
            sleep(backoffMs);
            backoffMs *= 2; // Exponential backoff

        } catch (InternalServerException e) {
            attempt++;
            if (attempt >= maxAttempts) {
                throw e;
            }

            System.err.println("Server error, retrying in " + backoffMs + "ms");
            sleep(backoffMs);
            backoffMs *= 2;

        } catch (AnthropicIoException e) {
            attempt++;
            if (attempt >= maxAttempts) {
                throw e;
            }

            System.err.println("Network error, retrying in " + backoffMs + "ms");
            sleep(backoffMs);
            backoffMs *= 2;
        }
    }

    throw new RuntimeException("Max retry attempts exceeded");
}

private void sleep(long ms) {
    try {
        Thread.sleep(ms);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(e);
    }
}

Circuit Breaker Pattern

import com.anthropic.errors.*;
import com.anthropic.models.messages.Message;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;

public class CircuitBreaker {
    private final int failureThreshold;
    private final long cooldownMs;
    private final AtomicInteger failures = new AtomicInteger(0);
    private volatile Instant lastFailureTime;
    private volatile boolean open = false;

    public CircuitBreaker(int failureThreshold, long cooldownMs) {
        this.failureThreshold = failureThreshold;
        this.cooldownMs = cooldownMs;
    }

    public Message create(MessageCreateParams params) {
        if (open) {
            if (Instant.now().toEpochMilli() - lastFailureTime.toEpochMilli()
                    > cooldownMs) {
                // Try to close circuit
                open = false;
                failures.set(0);
            } else {
                throw new RuntimeException("Circuit breaker is open");
            }
        }

        try {
            Message message = client.messages().create(params);
            failures.set(0); // Reset on success
            return message;

        } catch (RateLimitException | InternalServerException |
                 AnthropicIoException e) {
            int count = failures.incrementAndGet();
            lastFailureTime = Instant.now();

            if (count >= failureThreshold) {
                open = true;
                System.err.println("Circuit breaker opened after " + count +
                                   " failures");
            }
            throw e;
        }
    }
}

Best Practices

1. Log Request IDs

Always log request IDs for failed requests:

import com.anthropic.errors.AnthropicServiceException;

try {
    client.messages().create(params);
} catch (AnthropicServiceException e) {
    String requestId = e.headers().get("request-id")
        .stream().findFirst().orElse("unknown");

    System.err.println("Request failed: " + e.getMessage());
    System.err.println("Request ID: " + requestId);
    System.err.println("Status code: " + e.statusCode());

    // Log to monitoring system
    logger.error("Anthropic API error",
        "requestId", requestId,
        "statusCode", e.statusCode(),
        "message", e.getMessage());
}

2. Implement Monitoring

Track error rates and types:

import com.anthropic.errors.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public class ErrorMonitor {
    private final ConcurrentHashMap<String, AtomicLong> errorCounts
        = new ConcurrentHashMap<>();

    public void recordError(AnthropicException e) {
        String errorType = e.getClass().getSimpleName();
        errorCounts.computeIfAbsent(errorType, k -> new AtomicLong())
            .incrementAndGet();
    }

    public void printStats() {
        System.out.println("Error Statistics:");
        errorCounts.forEach((type, count) -> {
            System.out.println(type + ": " + count.get());
        });
    }
}

3. Handle Rate Limits Gracefully

import com.anthropic.errors.RateLimitException;

try {
    return client.messages().create(params);
} catch (RateLimitException e) {
    // Extract retry-after header
    String retryAfter = e.headers().get("retry-after")
        .stream().findFirst().orElse("60");

    long waitSeconds = Long.parseLong(retryAfter);
    System.out.println("Rate limited. Waiting " + waitSeconds + " seconds");

    // Queue for later or use rate limiter
    scheduleRetry(params, waitSeconds);
    return null;
}

4. Implement Graceful Degradation

import com.anthropic.errors.*;

public Message createMessageWithFallback(MessageCreateParams params) {
    try {
        return client.messages().create(params);

    } catch (RateLimitException e) {
        // Queue for later processing
        queueForLater(params);
        return createPlaceholderMessage("Queued due to rate limit");

    } catch (InternalServerException e) {
        // Use cached response if available
        return getCachedResponse(params)
            .orElseGet(() -> createPlaceholderMessage("Server error"));

    } catch (AnthropicException e) {
        // Log and return error response
        logError(e);
        return createErrorMessage(e.getMessage());
    }
}

5. Don't Retry Non-Retryable Errors

import com.anthropic.errors.*;

public boolean isRetryable(AnthropicException e) {
    return e instanceof RateLimitException
        || e instanceof InternalServerException
        || e instanceof AnthropicIoException
        || e instanceof AnthropicRetryableException;
}

public Message createWithConditionalRetry(MessageCreateParams params) {
    try {
        return client.messages().create(params);
    } catch (AnthropicException e) {
        if (isRetryable(e)) {
            // Retry logic
            return retryCreate(params);
        } else {
            // Don't retry, handle error
            System.err.println("Non-retryable error: " + e.getMessage());
            throw e;
        }
    }
}

6. Validate Responses When Needed

Enable response validation to catch data issues early:

import com.anthropic.core.RequestOptions;
import com.anthropic.models.messages.Message;

// Per-request validation
Message message = client.messages().create(
    params,
    RequestOptions.builder()
        .responseValidation(true)
        .build()
);

// Or configure globally
AnthropicClient client = AnthropicOkHttpClient.builder()
    .fromEnv()
    .responseValidation(true)
    .build();

7. Handle Streaming Interruptions

import com.anthropic.core.http.StreamResponse;
import com.anthropic.errors.SseException;
import com.anthropic.helpers.MessageAccumulator;

public Message createWithStreamRecovery(MessageCreateParams params) {
    MessageAccumulator accumulator = MessageAccumulator.create();

    try (StreamResponse<RawMessageStreamEvent> stream =
            client.messages().createStreaming(params)) {

        stream.stream()
            .peek(accumulator::accumulate)
            .forEach(this::processEvent);

        return accumulator.message();

    } catch (SseException e) {
        System.err.println("Stream interrupted: " + e.getMessage());

        // Return partial message if useful
        if (accumulator.message() != null) {
            System.out.println("Returning partial message");
            return accumulator.message();
        }
        throw e;
    }
}

8. Clean Up Resources

Always close clients and streams properly:

import com.anthropic.client.AnthropicClient;
import com.anthropic.core.http.StreamResponse;

// Close client when done
try (AnthropicClient client = AnthropicOkHttpClient.fromEnv()) {
    // Use client
} // Automatically closed

// Close streams
try (StreamResponse<RawMessageStreamEvent> stream =
        client.messages().createStreaming(params)) {
    stream.stream().forEach(this::processEvent);
} // Automatically closed

9. Provide Context in Errors

Add application context to error messages:

import com.anthropic.errors.AnthropicException;

public void processUserRequest(String userId, String requestText) {
    try {
        MessageCreateParams params = MessageCreateParams.builder()
            .addUserMessage(requestText)
            .model(Model.CLAUDE_SONNET_4_20250514)
            .maxTokens(1024L)
            .build();

        Message message = client.messages().create(params);
        saveResponse(userId, message);

    } catch (AnthropicException e) {
        // Include application context
        System.err.println("Failed to process request for user: " + userId);
        System.err.println("Request text length: " + requestText.length());
        System.err.println("Error: " + e.getMessage());

        if (e instanceof AnthropicServiceException) {
            AnthropicServiceException se = (AnthropicServiceException) e;
            System.err.println("Status: " + se.statusCode());

            String requestId = se.headers().get("request-id")
                .stream().findFirst().orElse("unknown");
            System.err.println("Request ID: " + requestId);
        }

        throw new RuntimeException("Failed to process request for user " + userId, e);
    }
}

Summary

The Anthropic Java SDK provides comprehensive error handling through a well-structured exception hierarchy:

  • Service exceptions map to specific HTTP status codes for precise error handling
  • Streaming exceptions handle SSE-specific errors during streaming operations
  • I/O exceptions capture network and connectivity issues
  • Retryable exceptions indicate transient failures that can be retried
  • Invalid data exceptions catch parsing and validation errors

All exceptions provide rich context including status codes, headers, and request IDs for debugging. The SDK automatically retries appropriate errors with exponential backoff, while allowing customization of retry behavior.

Following best practices like logging request IDs, implementing monitoring, handling rate limits gracefully, and using appropriate retry strategies will help build robust applications with the Anthropic SDK.

Install with Tessl CLI

npx tessl i tessl/maven-com-anthropic--anthropic-java@2.11.1

docs

client-setup.md

errors.md

index.md

messages.md

platform-adapters.md

streaming.md

structured-outputs.md

tools.md

tile.json