Java client for the Langfuse API providing access to observability and analytics features for LLM applications
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.
RuntimeException
└── LangfuseClientException
└── LangfuseClientApiException
├── Error (400 Bad Request)
├── UnauthorizedError (401 Unauthorized)
├── AccessDeniedError (403 Forbidden)
├── NotFoundError (404 Not Found)
├── MethodNotAllowedError (405 Method Not Allowed)
└── ServiceUnavailableError (503 Service Unavailable)Base exception for all SDK errors.
/**
* Base exception for Langfuse client errors
* Extends RuntimeException
*/
public class LangfuseClientException extends RuntimeException {
/**
* Create exception with message
*/
public LangfuseClientException(String message);
/**
* Create exception with message and cause
*/
public LangfuseClientException(String message, Exception cause);
}Base exception for non-2XX API responses.
/**
* Exception for non-2XX API responses
* Provides access to HTTP status code and response body
*/
public class LangfuseClientApiException extends LangfuseClientException {
/**
* Create API exception with status code and body
*/
public LangfuseClientApiException(String message, int statusCode, Object body);
/**
* Get HTTP status code
*/
public int statusCode();
/**
* Get response body
*/
public Object body();
/**
* String representation with status and body
*/
@Override
public String toString();
}/**
* Generic client error (400 Bad Request)
* Thrown for invalid requests
*/
public class Error extends LangfuseClientApiException {
public Error(Object body);
public int statusCode(); // Returns 400
public Object body();
}Common Causes:
/**
* Authentication failed (401 Unauthorized)
* Thrown when credentials are invalid or missing
*/
public class UnauthorizedError extends LangfuseClientApiException {
public UnauthorizedError(Object body);
public int statusCode(); // Returns 401
public Object body();
}Common Causes:
/**
* Permission denied (403 Forbidden)
* Thrown when authenticated but lacking permissions
*/
public class AccessDeniedError extends LangfuseClientApiException {
public AccessDeniedError(Object body);
public int statusCode(); // Returns 403
public Object body();
}Common Causes:
/**
* Resource not found (404 Not Found)
* Thrown when requested resource doesn't exist
*/
public class NotFoundError extends LangfuseClientApiException {
public NotFoundError(Object body);
public int statusCode(); // Returns 404
public Object body();
}Common Causes:
/**
* HTTP method not allowed (405 Method Not Allowed)
* Thrown when using wrong HTTP method for endpoint
*/
public class MethodNotAllowedError extends LangfuseClientApiException {
public MethodNotAllowedError(Object body);
public int statusCode(); // Returns 405
public Object body();
}Common Causes:
/**
* Service unavailable (503 Service Unavailable)
* Thrown by health endpoint when service is down
*/
public class ServiceUnavailableError extends LangfuseClientApiException {
public ServiceUnavailableError(Object body);
public int statusCode(); // Returns 503
public Object body();
}Common Causes:
import com.langfuse.client.LangfuseClient;
import com.langfuse.client.core.LangfuseClientApiException;
import com.langfuse.client.resources.commons.errors.*;
try {
var prompts = client.prompts().list();
} catch (LangfuseClientApiException e) {
System.err.println("API Error: " + e.statuscode());
System.err.println("Body: " + e.body());
}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.
import com.langfuse.client.resources.commons.errors.*;
try {
Trace trace = client.trace().get("invalid-id");
} catch (NotFoundError e) {
System.err.println("Trace not found: " + e.body());
} catch (UnauthorizedError e) {
System.err.println("Authentication failed: " + e.body());
} catch (AccessDeniedError e) {
System.err.println("Access denied: " + e.body());
} catch (LangfuseClientApiException e) {
System.err.println("API error: " + e.statusCode());
}import com.langfuse.client.resources.health.errors.ServiceUnavailableError;
public <T> T withRetry(Supplier<T> operation, int maxRetries) {
int attempts = 0;
while (true) {
try {
return operation.get();
} catch (ServiceUnavailableError e) {
attempts++;
if (attempts >= maxRetries) {
throw e;
}
try {
Thread.sleep(1000 * attempts); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
}
// Usage
Trace trace = withRetry(() -> client.trace().get("trace-123"), 3);public class LangfuseService {
private final LangfuseClient client;
public Optional<Prompt> getPrompt(String name) {
try {
return Optional.of(client.prompts().get(name));
} catch (NotFoundError e) {
return Optional.empty();
} catch (LangfuseClientApiException e) {
logger.error("Failed to get prompt: {}", e.statusCode());
return Optional.empty();
}
}
}import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LangfuseErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(LangfuseErrorHandler.class);
public void handleError(LangfuseClientApiException e) {
logger.error(
"Langfuse API error: status={}, body={}, message={}",
e.statusCode(),
e.body(),
e.getMessage()
);
// Send to error tracking service
if (e instanceof UnauthorizedError) {
// Alert on auth failures
alertAuthFailure(e);
} else if (e instanceof ServiceUnavailableError) {
// Alert on service outages
alertServiceDown(e);
}
}
private void alertAuthFailure(LangfuseClientApiException e) {
// Implementation
}
private void alertServiceDown(LangfuseClientApiException e) {
// Implementation
}
}import com.langfuse.client.LangfuseClient;
import com.langfuse.client.core.*;
import com.langfuse.client.resources.commons.errors.*;
import com.langfuse.client.resources.health.errors.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RobustLangfuseClient {
private static final Logger logger = LoggerFactory.getLogger(RobustLangfuseClient.class);
private final LangfuseClient client;
public RobustLangfuseClient(String url, String publicKey, String secretKey) {
this.client = LangfuseClient.builder()
.url(url)
.credentials(publicKey, secretKey)
.timeout(30)
.build();
}
public <T> Optional<T> executeWithErrorHandling(
Supplier<T> operation,
String operationName
) {
try {
T result = operation.get();
logger.debug("{} succeeded", operationName);
return Optional.of(result);
} catch (NotFoundError e) {
logger.warn("{} - Resource not found: {}",
operationName, e.body());
return Optional.empty();
} catch (UnauthorizedError e) {
logger.error("{} - Authentication failed: {}",
operationName, e.body());
throw new RuntimeException("Invalid Langfuse credentials", e);
} catch (AccessDeniedError e) {
logger.error("{} - Access denied: {}",
operationName, e.body());
throw new RuntimeException("Insufficient permissions", e);
} catch (com.langfuse.client.resources.commons.errors.Error e) {
logger.error("{} - Bad request (400): {}",
operationName, e.body());
return Optional.empty();
} catch (MethodNotAllowedError e) {
logger.error("{} - Method not allowed (405): {}",
operationName, e.body());
throw new RuntimeException("API method not allowed", e);
} catch (ServiceUnavailableError e) {
logger.warn("{} - Service unavailable (503): {}",
operationName, e.body());
return Optional.empty();
} catch (LangfuseClientApiException e) {
logger.error("{} - API error ({}): {}",
operationName, e.statusCode(), e.body());
return Optional.empty();
} catch (LangfuseClientException e) {
logger.error("{} - Client error: {}",
operationName, e.getMessage());
return Optional.empty();
} catch (Exception e) {
logger.error("{} - Unexpected error: {}",
operationName, e.getMessage(), e);
return Optional.empty();
}
}
public Optional<Trace> getTrace(String traceId) {
return executeWithErrorHandling(
() -> client.trace().get(traceId),
"getTrace(" + traceId + ")"
);
}
public Optional<PromptMetaListResponse> listPrompts() {
return executeWithErrorHandling(
() -> client.prompts().list(),
"listPrompts"
);
}
}try {
client.prompts().list();
} catch (UnauthorizedError e) {
System.err.println("Invalid API keys. Please check your credentials.");
}try {
Trace trace = client.trace().get(traceId);
} catch (NotFoundError e) {
logger.info("Trace {} does not exist", traceId);
}try {
Project project = client.projects().create(request);
} catch (AccessDeniedError e) {
System.err.println("Organization-scoped API key required for this operation");
}try {
HealthResponse health = client.health().health();
} catch (ServiceUnavailableError e) {
System.err.println("Langfuse service is temporarily unavailable");
// Fall back to cached data or queue operations
}import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@Test
public void testHandlesNotFoundError() {
LangfuseClient mockClient = mock(LangfuseClient.class);
TraceClient mockTraceClient = mock(TraceClient.class);
when(mockClient.trace()).thenReturn(mockTraceClient);
when(mockTraceClient.get("invalid-id"))
.thenThrow(new NotFoundError("Trace not found"));
RobustLangfuseClient client = new RobustLangfuseClient(mockClient);
Optional<Trace> result = client.getTrace("invalid-id");
assertTrue(result.isEmpty());
}Install with Tessl CLI
npx tessl i tessl/maven-com-langfuse--langfuse-java