0
# Exception Handling
1
2
The Langfuse Java client provides a comprehensive exception hierarchy for error handling. All exceptions extend from base types that include HTTP status codes and response bodies for debugging.
3
4
## Exception Hierarchy
5
6
```
7
RuntimeException
8
└── LangfuseClientException
9
└── LangfuseClientApiException
10
├── Error (400 Bad Request)
11
├── UnauthorizedError (401 Unauthorized)
12
├── AccessDeniedError (403 Forbidden)
13
├── NotFoundError (404 Not Found)
14
├── MethodNotAllowedError (405 Method Not Allowed)
15
└── ServiceUnavailableError (503 Service Unavailable)
16
```
17
18
## Base Exceptions
19
20
### LangfuseClientException
21
22
Base exception for all SDK errors.
23
24
```java { .api }
25
/**
26
* Base exception for Langfuse client errors
27
* Extends RuntimeException
28
*/
29
public class LangfuseClientException extends RuntimeException {
30
/**
31
* Create exception with message
32
*/
33
public LangfuseClientException(String message);
34
35
/**
36
* Create exception with message and cause
37
*/
38
public LangfuseClientException(String message, Exception cause);
39
}
40
```
41
42
### LangfuseClientApiException
43
44
Base exception for non-2XX API responses.
45
46
```java { .api }
47
/**
48
* Exception for non-2XX API responses
49
* Provides access to HTTP status code and response body
50
*/
51
public class LangfuseClientApiException extends LangfuseClientException {
52
/**
53
* Create API exception with status code and body
54
*/
55
public LangfuseClientApiException(String message, int statusCode, Object body);
56
57
/**
58
* Get HTTP status code
59
*/
60
public int statusCode();
61
62
/**
63
* Get response body
64
*/
65
public Object body();
66
67
/**
68
* String representation with status and body
69
*/
70
@Override
71
public String toString();
72
}
73
```
74
75
## HTTP Status Code Exceptions
76
77
### Error (400 Bad Request)
78
79
```java { .api }
80
/**
81
* Generic client error (400 Bad Request)
82
* Thrown for invalid requests
83
*/
84
public class Error extends LangfuseClientApiException {
85
public Error(Object body);
86
87
public int statusCode(); // Returns 400
88
public Object body();
89
}
90
```
91
92
**Common Causes:**
93
- Invalid request parameters
94
- Malformed JSON
95
- Missing required fields
96
- Invalid field values
97
98
### UnauthorizedError (401 Unauthorized)
99
100
```java { .api }
101
/**
102
* Authentication failed (401 Unauthorized)
103
* Thrown when credentials are invalid or missing
104
*/
105
public class UnauthorizedError extends LangfuseClientApiException {
106
public UnauthorizedError(Object body);
107
108
public int statusCode(); // Returns 401
109
public Object body();
110
}
111
```
112
113
**Common Causes:**
114
- Invalid API keys
115
- Expired credentials
116
- Missing Authorization header
117
- Incorrect Basic Auth format
118
119
### AccessDeniedError (403 Forbidden)
120
121
```java { .api }
122
/**
123
* Permission denied (403 Forbidden)
124
* Thrown when authenticated but lacking permissions
125
*/
126
public class AccessDeniedError extends LangfuseClientApiException {
127
public AccessDeniedError(Object body);
128
129
public int statusCode(); // Returns 403
130
public Object body();
131
}
132
```
133
134
**Common Causes:**
135
- Project-scoped key used for organization operations
136
- Insufficient role permissions
137
- Accessing another organization's resources
138
- Attempting to delete Langfuse-managed models
139
140
### NotFoundError (404 Not Found)
141
142
```java { .api }
143
/**
144
* Resource not found (404 Not Found)
145
* Thrown when requested resource doesn't exist
146
*/
147
public class NotFoundError extends LangfuseClientApiException {
148
public NotFoundError(Object body);
149
150
public int statusCode(); // Returns 404
151
public Object body();
152
}
153
```
154
155
**Common Causes:**
156
- Invalid trace/observation/prompt ID
157
- Deleted resource
158
- Typo in resource identifier
159
- Wrong project
160
161
### MethodNotAllowedError (405 Method Not Allowed)
162
163
```java { .api }
164
/**
165
* HTTP method not allowed (405 Method Not Allowed)
166
* Thrown when using wrong HTTP method for endpoint
167
*/
168
public class MethodNotAllowedError extends LangfuseClientApiException {
169
public MethodNotAllowedError(Object body);
170
171
public int statusCode(); // Returns 405
172
public Object body();
173
}
174
```
175
176
**Common Causes:**
177
- Using GET instead of POST
178
- Endpoint doesn't support the HTTP method
179
- Client/server version mismatch
180
181
### ServiceUnavailableError (503 Service Unavailable)
182
183
```java { .api }
184
/**
185
* Service unavailable (503 Service Unavailable)
186
* Thrown by health endpoint when service is down
187
*/
188
public class ServiceUnavailableError extends LangfuseClientApiException {
189
public ServiceUnavailableError(Object body);
190
191
public int statusCode(); // Returns 503
192
public Object body();
193
}
194
```
195
196
**Common Causes:**
197
- Database connection issues
198
- Service maintenance
199
- Temporary outage
200
- Network problems
201
202
## Error Handling Patterns
203
204
### Basic Try-Catch
205
206
```java
207
import com.langfuse.client.LangfuseClient;
208
import com.langfuse.client.core.LangfuseClientApiException;
209
import com.langfuse.client.resources.commons.errors.*;
210
211
try {
212
var prompts = client.prompts().list();
213
} catch (LangfuseClientApiException e) {
214
System.err.println("API Error: " + e.statuscode());
215
System.err.println("Body: " + e.body());
216
}
217
```
218
219
**Important Note on Error Class**: When using wildcard imports (`import com.langfuse.client.resources.commons.errors.*;`), be aware that the `Error` class from this package will conflict with `java.lang.Error`. In catch blocks, use the fully qualified name `com.langfuse.client.resources.commons.errors.Error` to avoid ambiguity.
220
221
### Specific Exception Handling
222
223
```java
224
import com.langfuse.client.resources.commons.errors.*;
225
226
try {
227
Trace trace = client.trace().get("invalid-id");
228
} catch (NotFoundError e) {
229
System.err.println("Trace not found: " + e.body());
230
} catch (UnauthorizedError e) {
231
System.err.println("Authentication failed: " + e.body());
232
} catch (AccessDeniedError e) {
233
System.err.println("Access denied: " + e.body());
234
} catch (LangfuseClientApiException e) {
235
System.err.println("API error: " + e.statusCode());
236
}
237
```
238
239
### Retry Logic
240
241
```java
242
import com.langfuse.client.resources.health.errors.ServiceUnavailableError;
243
244
public <T> T withRetry(Supplier<T> operation, int maxRetries) {
245
int attempts = 0;
246
while (true) {
247
try {
248
return operation.get();
249
} catch (ServiceUnavailableError e) {
250
attempts++;
251
if (attempts >= maxRetries) {
252
throw e;
253
}
254
try {
255
Thread.sleep(1000 * attempts); // Exponential backoff
256
} catch (InterruptedException ie) {
257
Thread.currentThread().interrupt();
258
throw new RuntimeException(ie);
259
}
260
}
261
}
262
}
263
264
// Usage
265
Trace trace = withRetry(() -> client.trace().get("trace-123"), 3);
266
```
267
268
### Graceful Degradation
269
270
```java
271
public class LangfuseService {
272
private final LangfuseClient client;
273
274
public Optional<Prompt> getPrompt(String name) {
275
try {
276
return Optional.of(client.prompts().get(name));
277
} catch (NotFoundError e) {
278
return Optional.empty();
279
} catch (LangfuseClientApiException e) {
280
logger.error("Failed to get prompt: {}", e.statusCode());
281
return Optional.empty();
282
}
283
}
284
}
285
```
286
287
### Error Logging
288
289
```java
290
import org.slf4j.Logger;
291
import org.slf4j.LoggerFactory;
292
293
public class LangfuseErrorHandler {
294
private static final Logger logger = LoggerFactory.getLogger(LangfuseErrorHandler.class);
295
296
public void handleError(LangfuseClientApiException e) {
297
logger.error(
298
"Langfuse API error: status={}, body={}, message={}",
299
e.statusCode(),
300
e.body(),
301
e.getMessage()
302
);
303
304
// Send to error tracking service
305
if (e instanceof UnauthorizedError) {
306
// Alert on auth failures
307
alertAuthFailure(e);
308
} else if (e instanceof ServiceUnavailableError) {
309
// Alert on service outages
310
alertServiceDown(e);
311
}
312
}
313
314
private void alertAuthFailure(LangfuseClientApiException e) {
315
// Implementation
316
}
317
318
private void alertServiceDown(LangfuseClientApiException e) {
319
// Implementation
320
}
321
}
322
```
323
324
## Complete Error Handling Example
325
326
```java
327
import com.langfuse.client.LangfuseClient;
328
import com.langfuse.client.core.*;
329
import com.langfuse.client.resources.commons.errors.*;
330
import com.langfuse.client.resources.health.errors.*;
331
import org.slf4j.Logger;
332
import org.slf4j.LoggerFactory;
333
334
public class RobustLangfuseClient {
335
private static final Logger logger = LoggerFactory.getLogger(RobustLangfuseClient.class);
336
private final LangfuseClient client;
337
338
public RobustLangfuseClient(String url, String publicKey, String secretKey) {
339
this.client = LangfuseClient.builder()
340
.url(url)
341
.credentials(publicKey, secretKey)
342
.timeout(30)
343
.build();
344
}
345
346
public <T> Optional<T> executeWithErrorHandling(
347
Supplier<T> operation,
348
String operationName
349
) {
350
try {
351
T result = operation.get();
352
logger.debug("{} succeeded", operationName);
353
return Optional.of(result);
354
355
} catch (NotFoundError e) {
356
logger.warn("{} - Resource not found: {}",
357
operationName, e.body());
358
return Optional.empty();
359
360
} catch (UnauthorizedError e) {
361
logger.error("{} - Authentication failed: {}",
362
operationName, e.body());
363
throw new RuntimeException("Invalid Langfuse credentials", e);
364
365
} catch (AccessDeniedError e) {
366
logger.error("{} - Access denied: {}",
367
operationName, e.body());
368
throw new RuntimeException("Insufficient permissions", e);
369
370
} catch (com.langfuse.client.resources.commons.errors.Error e) {
371
logger.error("{} - Bad request (400): {}",
372
operationName, e.body());
373
return Optional.empty();
374
375
} catch (MethodNotAllowedError e) {
376
logger.error("{} - Method not allowed (405): {}",
377
operationName, e.body());
378
throw new RuntimeException("API method not allowed", e);
379
380
} catch (ServiceUnavailableError e) {
381
logger.warn("{} - Service unavailable (503): {}",
382
operationName, e.body());
383
return Optional.empty();
384
385
} catch (LangfuseClientApiException e) {
386
logger.error("{} - API error ({}): {}",
387
operationName, e.statusCode(), e.body());
388
return Optional.empty();
389
390
} catch (LangfuseClientException e) {
391
logger.error("{} - Client error: {}",
392
operationName, e.getMessage());
393
return Optional.empty();
394
395
} catch (Exception e) {
396
logger.error("{} - Unexpected error: {}",
397
operationName, e.getMessage(), e);
398
return Optional.empty();
399
}
400
}
401
402
public Optional<Trace> getTrace(String traceId) {
403
return executeWithErrorHandling(
404
() -> client.trace().get(traceId),
405
"getTrace(" + traceId + ")"
406
);
407
}
408
409
public Optional<PromptMetaListResponse> listPrompts() {
410
return executeWithErrorHandling(
411
() -> client.prompts().list(),
412
"listPrompts"
413
);
414
}
415
}
416
```
417
418
## Best Practices
419
420
1. **Specific Exceptions First**: Catch specific exceptions before general ones
421
2. **Log Status Codes**: Always log HTTP status code and response body
422
3. **Graceful Degradation**: Return fallback values when appropriate
423
4. **Retry Logic**: Implement retries for transient failures (503)
424
5. **Don't Retry Auth Errors**: 401/403 errors won't be fixed by retrying
425
6. **Monitor Errors**: Track error rates and types for debugging
426
7. **User-Friendly Messages**: Convert technical errors to user-friendly messages
427
8. **Circuit Breaker**: Implement circuit breaker for repeated failures
428
429
## Common Error Scenarios
430
431
### Invalid API Keys
432
433
```java
434
try {
435
client.prompts().list();
436
} catch (UnauthorizedError e) {
437
System.err.println("Invalid API keys. Please check your credentials.");
438
}
439
```
440
441
### Resource Not Found
442
443
```java
444
try {
445
Trace trace = client.trace().get(traceId);
446
} catch (NotFoundError e) {
447
logger.info("Trace {} does not exist", traceId);
448
}
449
```
450
451
### Permission Denied
452
453
```java
454
try {
455
Project project = client.projects().create(request);
456
} catch (AccessDeniedError e) {
457
System.err.println("Organization-scoped API key required for this operation");
458
}
459
```
460
461
### Service Outage
462
463
```java
464
try {
465
HealthResponse health = client.health().health();
466
} catch (ServiceUnavailableError e) {
467
System.err.println("Langfuse service is temporarily unavailable");
468
// Fall back to cached data or queue operations
469
}
470
```
471
472
## Testing Error Handling
473
474
```java
475
import static org.mockito.Mockito.*;
476
import static org.junit.jupiter.api.Assertions.*;
477
478
@Test
479
public void testHandlesNotFoundError() {
480
LangfuseClient mockClient = mock(LangfuseClient.class);
481
TraceClient mockTraceClient = mock(TraceClient.class);
482
483
when(mockClient.trace()).thenReturn(mockTraceClient);
484
when(mockTraceClient.get("invalid-id"))
485
.thenThrow(new NotFoundError("Trace not found"));
486
487
RobustLangfuseClient client = new RobustLangfuseClient(mockClient);
488
Optional<Trace> result = client.getTrace("invalid-id");
489
490
assertTrue(result.isEmpty());
491
}
492
```
493
494
## Related Documentation
495
496
- [Client Configuration](./client-configuration.md) - Timeout and retry configuration
497
- [Health](./health.md) - ServiceUnavailableError handling
498