CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-langfuse--langfuse-java

Java client for the Langfuse API providing access to observability and analytics features for LLM applications

Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

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.

Exception Hierarchy

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 Exceptions

LangfuseClientException

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

LangfuseClientApiException

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();
}

HTTP Status Code Exceptions

Error (400 Bad Request)

/**
 * 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:

  • Invalid request parameters
  • Malformed JSON
  • Missing required fields
  • Invalid field values

UnauthorizedError (401 Unauthorized)

/**
 * 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:

  • Invalid API keys
  • Expired credentials
  • Missing Authorization header
  • Incorrect Basic Auth format

AccessDeniedError (403 Forbidden)

/**
 * 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:

  • Project-scoped key used for organization operations
  • Insufficient role permissions
  • Accessing another organization's resources
  • Attempting to delete Langfuse-managed models

NotFoundError (404 Not Found)

/**
 * 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:

  • Invalid trace/observation/prompt ID
  • Deleted resource
  • Typo in resource identifier
  • Wrong project

MethodNotAllowedError (405 Method Not Allowed)

/**
 * 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:

  • Using GET instead of POST
  • Endpoint doesn't support the HTTP method
  • Client/server version mismatch

ServiceUnavailableError (503 Service Unavailable)

/**
 * 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:

  • Database connection issues
  • Service maintenance
  • Temporary outage
  • Network problems

Error Handling Patterns

Basic Try-Catch

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.

Specific Exception Handling

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());
}

Retry Logic

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);

Graceful Degradation

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();
        }
    }
}

Error Logging

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
    }
}

Complete Error Handling Example

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"
        );
    }
}

Best Practices

  1. Specific Exceptions First: Catch specific exceptions before general ones
  2. Log Status Codes: Always log HTTP status code and response body
  3. Graceful Degradation: Return fallback values when appropriate
  4. Retry Logic: Implement retries for transient failures (503)
  5. Don't Retry Auth Errors: 401/403 errors won't be fixed by retrying
  6. Monitor Errors: Track error rates and types for debugging
  7. User-Friendly Messages: Convert technical errors to user-friendly messages
  8. Circuit Breaker: Implement circuit breaker for repeated failures

Common Error Scenarios

Invalid API Keys

try {
    client.prompts().list();
} catch (UnauthorizedError e) {
    System.err.println("Invalid API keys. Please check your credentials.");
}

Resource Not Found

try {
    Trace trace = client.trace().get(traceId);
} catch (NotFoundError e) {
    logger.info("Trace {} does not exist", traceId);
}

Permission Denied

try {
    Project project = client.projects().create(request);
} catch (AccessDeniedError e) {
    System.err.println("Organization-scoped API key required for this operation");
}

Service Outage

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
}

Testing Error Handling

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());
}

Related Documentation

  • Client Configuration - Timeout and retry configuration
  • Health - ServiceUnavailableError handling

Install with Tessl CLI

npx tessl i tessl/maven-com-langfuse--langfuse-java

docs

client-configuration.md

comments-annotations.md

common-types.md

datasets.md

exceptions.md

health.md

index.md

ingestion.md

media.md

metrics.md

models.md

pagination.md

projects-organizations.md

prompts.md

scim.md

scores.md

sessions.md

traces-observations.md

tile.json