0
# Error Handling
1
2
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.
3
4
## Exception Hierarchy
5
6
All Anthropic SDK exceptions extend from the base class:
7
8
```java { .api }
9
package com.anthropic.errors;
10
11
public class AnthropicException extends RuntimeException {
12
public AnthropicException(String message);
13
public AnthropicException(String message, Throwable cause);
14
}
15
```
16
17
The exception hierarchy is organized into several categories:
18
19
```
20
AnthropicException (base)
21
├── AnthropicServiceException (HTTP errors)
22
│ ├── BadRequestException (400)
23
│ ├── UnauthorizedException (401)
24
│ ├── PermissionDeniedException (403)
25
│ ├── NotFoundException (404)
26
│ ├── UnprocessableEntityException (422)
27
│ ├── RateLimitException (429)
28
│ ├── InternalServerException (5xx)
29
│ ├── UnexpectedStatusCodeException (other status codes)
30
│ └── SseException (streaming errors)
31
├── AnthropicIoException (network/I/O errors)
32
├── AnthropicRetryableException (retryable failures)
33
└── AnthropicInvalidDataException (parsing/validation errors)
34
```
35
36
## Service Exceptions
37
38
Service exceptions represent HTTP errors returned by the Anthropic API. All service exceptions extend `AnthropicServiceException`:
39
40
```java { .api }
41
package com.anthropic.errors;
42
43
import com.anthropic.core.http.Headers;
44
45
public class AnthropicServiceException extends AnthropicException {
46
private final int statusCode;
47
private final Headers headers;
48
49
public int statusCode();
50
public Headers headers();
51
}
52
```
53
54
### BadRequestException (400)
55
56
Indicates invalid request parameters or malformed request data:
57
58
```java { .api }
59
package com.anthropic.errors;
60
61
public class BadRequestException extends AnthropicServiceException {
62
public BadRequestException(
63
int statusCode,
64
Headers headers,
65
String message,
66
Throwable cause
67
);
68
}
69
```
70
71
**Common causes:**
72
- Invalid parameter values
73
- Malformed JSON in request body
74
- Missing required parameters
75
- Parameters that conflict with each other
76
77
**Example:**
78
79
```java
80
import com.anthropic.errors.BadRequestException;
81
import com.anthropic.models.messages.MessageCreateParams;
82
import com.anthropic.models.messages.Model;
83
84
try {
85
MessageCreateParams params = MessageCreateParams.builder()
86
.maxTokens(0L) // Invalid: must be positive
87
.addUserMessage("Hello")
88
.model(Model.CLAUDE_SONNET_4_20250514)
89
.build();
90
91
client.messages().create(params);
92
} catch (BadRequestException e) {
93
System.err.println("Bad request: " + e.getMessage());
94
System.err.println("Status code: " + e.statusCode());
95
}
96
```
97
98
### UnauthorizedException (401)
99
100
Indicates authentication failure:
101
102
```java { .api }
103
package com.anthropic.errors;
104
105
public class UnauthorizedException extends AnthropicServiceException {
106
public UnauthorizedException(
107
int statusCode,
108
Headers headers,
109
String message,
110
Throwable cause
111
);
112
}
113
```
114
115
**Common causes:**
116
- Missing API key
117
- Invalid API key
118
- Expired authentication token
119
120
**Example:**
121
122
```java
123
import com.anthropic.errors.UnauthorizedException;
124
125
try {
126
Message message = client.messages().create(params);
127
} catch (UnauthorizedException e) {
128
System.err.println("Authentication failed: " + e.getMessage());
129
// Log for debugging, check API key configuration
130
}
131
```
132
133
### PermissionDeniedException (403)
134
135
Indicates insufficient permissions for the requested operation:
136
137
```java { .api }
138
package com.anthropic.errors;
139
140
public class PermissionDeniedException extends AnthropicServiceException {
141
public PermissionDeniedException(
142
int statusCode,
143
Headers headers,
144
String message,
145
Throwable cause
146
);
147
}
148
```
149
150
**Common causes:**
151
- API key lacks necessary permissions
152
- Access to specific model or feature denied
153
- Organization or account restrictions
154
155
**Example:**
156
157
```java
158
import com.anthropic.errors.PermissionDeniedException;
159
160
try {
161
Message message = client.messages().create(params);
162
} catch (PermissionDeniedException e) {
163
System.err.println("Permission denied: " + e.getMessage());
164
// Check account permissions and model access
165
}
166
```
167
168
### NotFoundException (404)
169
170
Indicates the requested resource does not exist:
171
172
```java { .api }
173
package com.anthropic.errors;
174
175
public class NotFoundException extends AnthropicServiceException {
176
public NotFoundException(
177
int statusCode,
178
Headers headers,
179
String message,
180
Throwable cause
181
);
182
}
183
```
184
185
**Common causes:**
186
- Invalid resource ID
187
- Resource has been deleted
188
- Incorrect endpoint path
189
190
**Example:**
191
192
```java
193
import com.anthropic.errors.NotFoundException;
194
195
try {
196
MessageBatch batch = client.messages().batches().retrieve("invalid-id");
197
} catch (NotFoundException e) {
198
System.err.println("Resource not found: " + e.getMessage());
199
// Handle missing resource gracefully
200
}
201
```
202
203
### UnprocessableEntityException (422)
204
205
Indicates the request was well-formed but contains semantic errors:
206
207
```java { .api }
208
package com.anthropic.errors;
209
210
public class UnprocessableEntityException extends AnthropicServiceException {
211
public UnprocessableEntityException(
212
int statusCode,
213
Headers headers,
214
String message,
215
Throwable cause
216
);
217
}
218
```
219
220
**Common causes:**
221
- Invalid tool definitions
222
- Malformed JSON schema
223
- Invalid model parameters
224
- Business logic validation failures
225
226
**Example:**
227
228
```java
229
import com.anthropic.errors.UnprocessableEntityException;
230
231
try {
232
Message message = client.messages().create(params);
233
} catch (UnprocessableEntityException e) {
234
System.err.println("Unprocessable entity: " + e.getMessage());
235
// Review request parameters and tool definitions
236
}
237
```
238
239
### RateLimitException (429)
240
241
Indicates rate limit has been exceeded:
242
243
```java { .api }
244
package com.anthropic.errors;
245
246
public class RateLimitException extends AnthropicServiceException {
247
public RateLimitException(
248
int statusCode,
249
Headers headers,
250
String message,
251
Throwable cause
252
);
253
}
254
```
255
256
**Common causes:**
257
- Too many requests in a time window
258
- Token usage limits exceeded
259
- Concurrent request limits exceeded
260
261
**Example:**
262
263
```java
264
import com.anthropic.errors.RateLimitException;
265
import java.util.Optional;
266
267
try {
268
Message message = client.messages().create(params);
269
} catch (RateLimitException e) {
270
System.err.println("Rate limit exceeded: " + e.getMessage());
271
272
// Check for retry-after header
273
Optional<String> retryAfter = e.headers().get("retry-after").stream().findFirst();
274
if (retryAfter.isPresent()) {
275
int seconds = Integer.parseInt(retryAfter.get());
276
System.out.println("Retry after " + seconds + " seconds");
277
}
278
}
279
```
280
281
### InternalServerException (5xx)
282
283
Indicates a server-side error:
284
285
```java { .api }
286
package com.anthropic.errors;
287
288
public class InternalServerException extends AnthropicServiceException {
289
public InternalServerException(
290
int statusCode,
291
Headers headers,
292
String message,
293
Throwable cause
294
);
295
}
296
```
297
298
**Common causes:**
299
- Temporary server issues
300
- Service overload
301
- Internal server errors
302
303
**Example:**
304
305
```java
306
import com.anthropic.errors.InternalServerException;
307
308
try {
309
Message message = client.messages().create(params);
310
} catch (InternalServerException e) {
311
System.err.println("Server error: " + e.getMessage());
312
System.err.println("Status code: " + e.statusCode());
313
// Implement retry logic with exponential backoff
314
}
315
```
316
317
### UnexpectedStatusCodeException
318
319
Handles HTTP status codes not covered by specific exception types:
320
321
```java { .api }
322
package com.anthropic.errors;
323
324
public class UnexpectedStatusCodeException extends AnthropicServiceException {
325
public UnexpectedStatusCodeException(
326
int statusCode,
327
Headers headers,
328
String message,
329
Throwable cause
330
);
331
}
332
```
333
334
**Example:**
335
336
```java
337
import com.anthropic.errors.UnexpectedStatusCodeException;
338
339
try {
340
Message message = client.messages().create(params);
341
} catch (UnexpectedStatusCodeException e) {
342
System.err.println("Unexpected status code: " + e.statusCode());
343
System.err.println("Message: " + e.getMessage());
344
}
345
```
346
347
## Streaming Exceptions
348
349
### SseException
350
351
Thrown for errors that occur during Server-Sent Events (SSE) streaming after a successful initial HTTP response:
352
353
```java { .api }
354
package com.anthropic.errors;
355
356
public class SseException extends AnthropicServiceException {
357
public SseException(
358
int statusCode,
359
Headers headers,
360
String message,
361
Throwable cause
362
);
363
}
364
```
365
366
**Common causes:**
367
- Connection interruption during streaming
368
- Malformed SSE data
369
- Server-side streaming errors
370
- Network issues mid-stream
371
372
**Example:**
373
374
```java
375
import com.anthropic.core.http.StreamResponse;
376
import com.anthropic.errors.SseException;
377
import com.anthropic.models.messages.RawMessageStreamEvent;
378
379
try (StreamResponse<RawMessageStreamEvent> stream =
380
client.messages().createStreaming(params)) {
381
382
stream.stream().forEach(event -> {
383
// Process streaming events
384
System.out.println(event);
385
});
386
} catch (SseException e) {
387
System.err.println("Streaming error: " + e.getMessage());
388
// Handle interrupted stream, may need to retry
389
}
390
```
391
392
## I/O Exceptions
393
394
### AnthropicIoException
395
396
Represents network and I/O errors during communication with the API:
397
398
```java { .api }
399
package com.anthropic.errors;
400
401
public class AnthropicIoException extends AnthropicException {
402
public AnthropicIoException(String message);
403
public AnthropicIoException(String message, Throwable cause);
404
}
405
```
406
407
**Common causes:**
408
- Network connectivity problems
409
- DNS resolution failures
410
- Connection timeouts
411
- SSL/TLS handshake errors
412
- Socket errors
413
414
**Example:**
415
416
```java
417
import com.anthropic.errors.AnthropicIoException;
418
419
try {
420
Message message = client.messages().create(params);
421
} catch (AnthropicIoException e) {
422
System.err.println("I/O error: " + e.getMessage());
423
// Check network connectivity
424
// Verify proxy settings
425
// Consider retry with backoff
426
}
427
```
428
429
## Retryable Exceptions
430
431
### AnthropicRetryableException
432
433
Indicates a failure that could be retried by the client:
434
435
```java { .api }
436
package com.anthropic.errors;
437
438
public class AnthropicRetryableException extends AnthropicException {
439
public AnthropicRetryableException(String message);
440
public AnthropicRetryableException(String message, Throwable cause);
441
}
442
```
443
444
**When thrown:**
445
- Generic retryable failures
446
- Transient errors not covered by specific exception types
447
448
**Example:**
449
450
```java
451
import com.anthropic.errors.AnthropicRetryableException;
452
453
try {
454
Message message = client.messages().create(params);
455
} catch (AnthropicRetryableException e) {
456
System.err.println("Retryable error: " + e.getMessage());
457
// Implement custom retry logic
458
}
459
```
460
461
## Invalid Data Exceptions
462
463
### AnthropicInvalidDataException
464
465
Thrown when successfully parsed data cannot be interpreted correctly:
466
467
```java { .api }
468
package com.anthropic.errors;
469
470
public class AnthropicInvalidDataException extends AnthropicException {
471
public AnthropicInvalidDataException(String message);
472
public AnthropicInvalidDataException(String message, Throwable cause);
473
}
474
```
475
476
**Common causes:**
477
- Missing required properties in API response
478
- Unexpected data types in response
479
- Invalid enum values
480
- Schema validation failures
481
- Response structure doesn't match SDK expectations
482
483
**Example:**
484
485
```java
486
import com.anthropic.errors.AnthropicInvalidDataException;
487
488
try {
489
Message message = client.messages().create(params);
490
491
// Accessing required property that API unexpectedly omitted
492
String id = message.id();
493
} catch (AnthropicInvalidDataException e) {
494
System.err.println("Invalid data: " + e.getMessage());
495
// API returned unexpected data format
496
// May need SDK update
497
}
498
```
499
500
## Error Properties
501
502
All service exceptions provide access to response metadata:
503
504
### Status Code
505
506
```java
507
import com.anthropic.errors.AnthropicServiceException;
508
509
try {
510
client.messages().create(params);
511
} catch (AnthropicServiceException e) {
512
int statusCode = e.statusCode();
513
System.err.println("HTTP status: " + statusCode);
514
}
515
```
516
517
### Headers
518
519
Access response headers for additional error context:
520
521
```java
522
import com.anthropic.core.http.Headers;
523
import com.anthropic.errors.AnthropicServiceException;
524
525
try {
526
client.messages().create(params);
527
} catch (AnthropicServiceException e) {
528
Headers headers = e.headers();
529
530
// Get specific header
531
headers.get("x-error-type").stream()
532
.findFirst()
533
.ifPresent(type -> System.err.println("Error type: " + type));
534
535
// Iterate all headers
536
headers.names().forEach(name -> {
537
headers.get(name).forEach(value -> {
538
System.err.println(name + ": " + value);
539
});
540
});
541
}
542
```
543
544
## Request IDs
545
546
Request IDs are crucial for debugging. They can be obtained from raw responses or error headers:
547
548
### From Raw Responses
549
550
```java
551
import com.anthropic.core.http.HttpResponseFor;
552
import com.anthropic.models.messages.Message;
553
import java.util.Optional;
554
555
HttpResponseFor<Message> response = client.messages()
556
.withRawResponse()
557
.create(params);
558
559
Optional<String> requestId = response.requestId();
560
requestId.ifPresent(id ->
561
System.out.println("Request ID: " + id)
562
);
563
```
564
565
### From Exception Headers
566
567
```java
568
import com.anthropic.errors.AnthropicServiceException;
569
import java.util.Optional;
570
571
try {
572
client.messages().create(params);
573
} catch (AnthropicServiceException e) {
574
Optional<String> requestId = e.headers()
575
.get("request-id")
576
.stream()
577
.findFirst();
578
579
if (requestId.isPresent()) {
580
System.err.println("Request ID for debugging: " + requestId.get());
581
// Include this in bug reports to Anthropic
582
}
583
}
584
```
585
586
## Retry Behavior
587
588
The SDK automatically retries certain errors with exponential backoff:
589
590
### Automatically Retried Errors
591
592
The following error types trigger automatic retries (default: 2 retries):
593
594
- **Connection errors** - Network connectivity problems
595
- **408 Request Timeout** - Server request timeout
596
- **409 Conflict** - Resource conflict
597
- **429 Rate Limit** - Rate limit exceeded
598
- **5xx Internal Server** - Server-side errors
599
600
### Configuring Retries
601
602
Set custom retry behavior at the client level:
603
604
```java
605
import com.anthropic.client.AnthropicClient;
606
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
607
608
AnthropicClient client = AnthropicOkHttpClient.builder()
609
.fromEnv()
610
.maxRetries(4) // Increase from default 2
611
.build();
612
```
613
614
Disable retries:
615
616
```java
617
AnthropicClient client = AnthropicOkHttpClient.builder()
618
.fromEnv()
619
.maxRetries(0) // Disable automatic retries
620
.build();
621
```
622
623
### Non-Retried Errors
624
625
These errors are NOT automatically retried:
626
627
- **400 Bad Request** - Fix request parameters
628
- **401 Unauthorized** - Fix authentication
629
- **403 Permission Denied** - Fix permissions
630
- **404 Not Found** - Resource doesn't exist
631
- **422 Unprocessable Entity** - Fix request semantics
632
633
## Error Handling Patterns
634
635
### Basic Try-Catch
636
637
```java
638
import com.anthropic.errors.AnthropicException;
639
import com.anthropic.models.messages.Message;
640
641
try {
642
Message message = client.messages().create(params);
643
System.out.println("Success: " + message.id());
644
} catch (AnthropicException e) {
645
System.err.println("Error: " + e.getMessage());
646
e.printStackTrace();
647
}
648
```
649
650
### Specific Exception Handling
651
652
```java
653
import com.anthropic.errors.*;
654
655
try {
656
Message message = client.messages().create(params);
657
processMessage(message);
658
659
} catch (UnauthorizedException e) {
660
System.err.println("Authentication failed. Check API key.");
661
// Notify administrator
662
663
} catch (RateLimitException e) {
664
System.err.println("Rate limit exceeded. Backing off...");
665
// Implement backoff strategy
666
667
} catch (InternalServerException e) {
668
System.err.println("Server error. Will retry.");
669
// Implement retry logic
670
671
} catch (AnthropicIoException e) {
672
System.err.println("Network error: " + e.getMessage());
673
// Check connectivity
674
675
} catch (AnthropicException e) {
676
System.err.println("Unexpected error: " + e.getMessage());
677
// Log for investigation
678
}
679
```
680
681
### Async Error Handling
682
683
```java
684
import com.anthropic.errors.AnthropicException;
685
import java.util.concurrent.CompletableFuture;
686
687
CompletableFuture<Message> future = client.async().messages().create(params);
688
689
future.handle((message, error) -> {
690
if (error != null) {
691
if (error instanceof RateLimitException) {
692
System.err.println("Rate limited");
693
} else if (error instanceof AnthropicIoException) {
694
System.err.println("Network error");
695
} else {
696
System.err.println("Error: " + error.getMessage());
697
}
698
return null;
699
}
700
701
// Process successful message
702
System.out.println("Success: " + message.id());
703
return message;
704
});
705
```
706
707
### Streaming Error Handling
708
709
```java
710
import com.anthropic.core.http.StreamResponse;
711
import com.anthropic.errors.SseException;
712
import com.anthropic.models.messages.RawMessageStreamEvent;
713
714
try (StreamResponse<RawMessageStreamEvent> stream =
715
client.messages().createStreaming(params)) {
716
717
stream.stream().forEach(event -> {
718
try {
719
processEvent(event);
720
} catch (Exception e) {
721
System.err.println("Error processing event: " + e.getMessage());
722
// Continue processing other events
723
}
724
});
725
726
} catch (SseException e) {
727
System.err.println("Streaming interrupted: " + e.getMessage());
728
// Attempt to resume or restart stream
729
730
} catch (AnthropicException e) {
731
System.err.println("Error during streaming: " + e.getMessage());
732
}
733
```
734
735
### Retry with Exponential Backoff
736
737
```java
738
import com.anthropic.errors.*;
739
import com.anthropic.models.messages.Message;
740
import java.time.Duration;
741
742
public Message createWithRetry(MessageCreateParams params, int maxAttempts) {
743
int attempt = 0;
744
long backoffMs = 1000; // Start with 1 second
745
746
while (attempt < maxAttempts) {
747
try {
748
return client.messages().create(params);
749
750
} catch (RateLimitException e) {
751
attempt++;
752
if (attempt >= maxAttempts) {
753
throw e;
754
}
755
756
System.err.println("Rate limited, retrying in " + backoffMs + "ms");
757
sleep(backoffMs);
758
backoffMs *= 2; // Exponential backoff
759
760
} catch (InternalServerException e) {
761
attempt++;
762
if (attempt >= maxAttempts) {
763
throw e;
764
}
765
766
System.err.println("Server error, retrying in " + backoffMs + "ms");
767
sleep(backoffMs);
768
backoffMs *= 2;
769
770
} catch (AnthropicIoException e) {
771
attempt++;
772
if (attempt >= maxAttempts) {
773
throw e;
774
}
775
776
System.err.println("Network error, retrying in " + backoffMs + "ms");
777
sleep(backoffMs);
778
backoffMs *= 2;
779
}
780
}
781
782
throw new RuntimeException("Max retry attempts exceeded");
783
}
784
785
private void sleep(long ms) {
786
try {
787
Thread.sleep(ms);
788
} catch (InterruptedException e) {
789
Thread.currentThread().interrupt();
790
throw new RuntimeException(e);
791
}
792
}
793
```
794
795
### Circuit Breaker Pattern
796
797
```java
798
import com.anthropic.errors.*;
799
import com.anthropic.models.messages.Message;
800
import java.time.Instant;
801
import java.util.concurrent.atomic.AtomicInteger;
802
803
public class CircuitBreaker {
804
private final int failureThreshold;
805
private final long cooldownMs;
806
private final AtomicInteger failures = new AtomicInteger(0);
807
private volatile Instant lastFailureTime;
808
private volatile boolean open = false;
809
810
public CircuitBreaker(int failureThreshold, long cooldownMs) {
811
this.failureThreshold = failureThreshold;
812
this.cooldownMs = cooldownMs;
813
}
814
815
public Message create(MessageCreateParams params) {
816
if (open) {
817
if (Instant.now().toEpochMilli() - lastFailureTime.toEpochMilli()
818
> cooldownMs) {
819
// Try to close circuit
820
open = false;
821
failures.set(0);
822
} else {
823
throw new RuntimeException("Circuit breaker is open");
824
}
825
}
826
827
try {
828
Message message = client.messages().create(params);
829
failures.set(0); // Reset on success
830
return message;
831
832
} catch (RateLimitException | InternalServerException |
833
AnthropicIoException e) {
834
int count = failures.incrementAndGet();
835
lastFailureTime = Instant.now();
836
837
if (count >= failureThreshold) {
838
open = true;
839
System.err.println("Circuit breaker opened after " + count +
840
" failures");
841
}
842
throw e;
843
}
844
}
845
}
846
```
847
848
## Best Practices
849
850
### 1. Log Request IDs
851
852
Always log request IDs for failed requests:
853
854
```java
855
import com.anthropic.errors.AnthropicServiceException;
856
857
try {
858
client.messages().create(params);
859
} catch (AnthropicServiceException e) {
860
String requestId = e.headers().get("request-id")
861
.stream().findFirst().orElse("unknown");
862
863
System.err.println("Request failed: " + e.getMessage());
864
System.err.println("Request ID: " + requestId);
865
System.err.println("Status code: " + e.statusCode());
866
867
// Log to monitoring system
868
logger.error("Anthropic API error",
869
"requestId", requestId,
870
"statusCode", e.statusCode(),
871
"message", e.getMessage());
872
}
873
```
874
875
### 2. Implement Monitoring
876
877
Track error rates and types:
878
879
```java
880
import com.anthropic.errors.*;
881
import java.util.concurrent.ConcurrentHashMap;
882
import java.util.concurrent.atomic.AtomicLong;
883
884
public class ErrorMonitor {
885
private final ConcurrentHashMap<String, AtomicLong> errorCounts
886
= new ConcurrentHashMap<>();
887
888
public void recordError(AnthropicException e) {
889
String errorType = e.getClass().getSimpleName();
890
errorCounts.computeIfAbsent(errorType, k -> new AtomicLong())
891
.incrementAndGet();
892
}
893
894
public void printStats() {
895
System.out.println("Error Statistics:");
896
errorCounts.forEach((type, count) -> {
897
System.out.println(type + ": " + count.get());
898
});
899
}
900
}
901
```
902
903
### 3. Handle Rate Limits Gracefully
904
905
```java
906
import com.anthropic.errors.RateLimitException;
907
908
try {
909
return client.messages().create(params);
910
} catch (RateLimitException e) {
911
// Extract retry-after header
912
String retryAfter = e.headers().get("retry-after")
913
.stream().findFirst().orElse("60");
914
915
long waitSeconds = Long.parseLong(retryAfter);
916
System.out.println("Rate limited. Waiting " + waitSeconds + " seconds");
917
918
// Queue for later or use rate limiter
919
scheduleRetry(params, waitSeconds);
920
return null;
921
}
922
```
923
924
### 4. Implement Graceful Degradation
925
926
```java
927
import com.anthropic.errors.*;
928
929
public Message createMessageWithFallback(MessageCreateParams params) {
930
try {
931
return client.messages().create(params);
932
933
} catch (RateLimitException e) {
934
// Queue for later processing
935
queueForLater(params);
936
return createPlaceholderMessage("Queued due to rate limit");
937
938
} catch (InternalServerException e) {
939
// Use cached response if available
940
return getCachedResponse(params)
941
.orElseGet(() -> createPlaceholderMessage("Server error"));
942
943
} catch (AnthropicException e) {
944
// Log and return error response
945
logError(e);
946
return createErrorMessage(e.getMessage());
947
}
948
}
949
```
950
951
### 5. Don't Retry Non-Retryable Errors
952
953
```java
954
import com.anthropic.errors.*;
955
956
public boolean isRetryable(AnthropicException e) {
957
return e instanceof RateLimitException
958
|| e instanceof InternalServerException
959
|| e instanceof AnthropicIoException
960
|| e instanceof AnthropicRetryableException;
961
}
962
963
public Message createWithConditionalRetry(MessageCreateParams params) {
964
try {
965
return client.messages().create(params);
966
} catch (AnthropicException e) {
967
if (isRetryable(e)) {
968
// Retry logic
969
return retryCreate(params);
970
} else {
971
// Don't retry, handle error
972
System.err.println("Non-retryable error: " + e.getMessage());
973
throw e;
974
}
975
}
976
}
977
```
978
979
### 6. Validate Responses When Needed
980
981
Enable response validation to catch data issues early:
982
983
```java
984
import com.anthropic.core.RequestOptions;
985
import com.anthropic.models.messages.Message;
986
987
// Per-request validation
988
Message message = client.messages().create(
989
params,
990
RequestOptions.builder()
991
.responseValidation(true)
992
.build()
993
);
994
995
// Or configure globally
996
AnthropicClient client = AnthropicOkHttpClient.builder()
997
.fromEnv()
998
.responseValidation(true)
999
.build();
1000
```
1001
1002
### 7. Handle Streaming Interruptions
1003
1004
```java
1005
import com.anthropic.core.http.StreamResponse;
1006
import com.anthropic.errors.SseException;
1007
import com.anthropic.helpers.MessageAccumulator;
1008
1009
public Message createWithStreamRecovery(MessageCreateParams params) {
1010
MessageAccumulator accumulator = MessageAccumulator.create();
1011
1012
try (StreamResponse<RawMessageStreamEvent> stream =
1013
client.messages().createStreaming(params)) {
1014
1015
stream.stream()
1016
.peek(accumulator::accumulate)
1017
.forEach(this::processEvent);
1018
1019
return accumulator.message();
1020
1021
} catch (SseException e) {
1022
System.err.println("Stream interrupted: " + e.getMessage());
1023
1024
// Return partial message if useful
1025
if (accumulator.message() != null) {
1026
System.out.println("Returning partial message");
1027
return accumulator.message();
1028
}
1029
throw e;
1030
}
1031
}
1032
```
1033
1034
### 8. Clean Up Resources
1035
1036
Always close clients and streams properly:
1037
1038
```java
1039
import com.anthropic.client.AnthropicClient;
1040
import com.anthropic.core.http.StreamResponse;
1041
1042
// Close client when done
1043
try (AnthropicClient client = AnthropicOkHttpClient.fromEnv()) {
1044
// Use client
1045
} // Automatically closed
1046
1047
// Close streams
1048
try (StreamResponse<RawMessageStreamEvent> stream =
1049
client.messages().createStreaming(params)) {
1050
stream.stream().forEach(this::processEvent);
1051
} // Automatically closed
1052
```
1053
1054
### 9. Provide Context in Errors
1055
1056
Add application context to error messages:
1057
1058
```java
1059
import com.anthropic.errors.AnthropicException;
1060
1061
public void processUserRequest(String userId, String requestText) {
1062
try {
1063
MessageCreateParams params = MessageCreateParams.builder()
1064
.addUserMessage(requestText)
1065
.model(Model.CLAUDE_SONNET_4_20250514)
1066
.maxTokens(1024L)
1067
.build();
1068
1069
Message message = client.messages().create(params);
1070
saveResponse(userId, message);
1071
1072
} catch (AnthropicException e) {
1073
// Include application context
1074
System.err.println("Failed to process request for user: " + userId);
1075
System.err.println("Request text length: " + requestText.length());
1076
System.err.println("Error: " + e.getMessage());
1077
1078
if (e instanceof AnthropicServiceException) {
1079
AnthropicServiceException se = (AnthropicServiceException) e;
1080
System.err.println("Status: " + se.statusCode());
1081
1082
String requestId = se.headers().get("request-id")
1083
.stream().findFirst().orElse("unknown");
1084
System.err.println("Request ID: " + requestId);
1085
}
1086
1087
throw new RuntimeException("Failed to process request for user " + userId, e);
1088
}
1089
}
1090
```
1091
1092
## Summary
1093
1094
The Anthropic Java SDK provides comprehensive error handling through a well-structured exception hierarchy:
1095
1096
- **Service exceptions** map to specific HTTP status codes for precise error handling
1097
- **Streaming exceptions** handle SSE-specific errors during streaming operations
1098
- **I/O exceptions** capture network and connectivity issues
1099
- **Retryable exceptions** indicate transient failures that can be retried
1100
- **Invalid data exceptions** catch parsing and validation errors
1101
1102
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.
1103
1104
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.
1105