LangChain4j Agentic Framework provides a comprehensive Java library for building multi-agent AI systems with support for workflow orchestration, supervisor agents, planning-based execution, declarative configuration, agent-to-agent communication, and human-in-the-loop workflows.
Comprehensive error handling patterns for workflows with retry, recovery, and fallback strategies.
Error handling enables:
<S> S errorHandler(Function<ErrorContext, ErrorRecoveryResult> errorHandler);Available on all workflow builders:
agentBuilder()sequenceBuilder()parallelBuilder()loopBuilder()conditionalBuilder()supervisorBuilder()plannerBuilder()record ErrorContext(
String agentName,
AgenticScope agenticScope,
AgentInvocationException exception
) {}Access error details:
.errorHandler(ctx -> {
String failedAgent = ctx.agentName();
AgenticScope scope = ctx.agenticScope();
AgentInvocationException error = ctx.exception();
System.err.println("Agent: " + failedAgent);
System.err.println("Error: " + error.getMessage());
System.err.println("State: " + scope.state());
return ErrorRecoveryResult.retry();
})record ErrorRecoveryResult(Type type, Object result) {
enum Type {
THROW_EXCEPTION, // Re-throw the exception
RETURN_RESULT, // Return result and continue
RETRY // Retry the failed operation
}
static ErrorRecoveryResult throwException();
static ErrorRecoveryResult retry();
static ErrorRecoveryResult result(Object result);
}UntypedAgent retryAgent = AgenticServices.sequenceBuilder()
.subAgents(apiCaller)
.errorHandler(ctx -> {
AgenticScope scope = ctx.agenticScope();
int retryCount = scope.readState("retry_count", 0);
if (retryCount < 3) {
scope.writeState("retry_count", retryCount + 1);
return ErrorRecoveryResult.retry();
}
return ErrorRecoveryResult.throwException();
})
.build();UntypedAgent fallbackAgent = AgenticServices.conditionalBuilder()
.subAgents(primaryAgent)
.errorHandler(ctx -> {
System.err.println("Primary failed, using fallback");
return ErrorRecoveryResult.result("fallback-value");
})
.build();UntypedAgent smartAgent = AgenticServices.loopBuilder()
.subAgents(processor)
.maxIterations(5)
.errorHandler(ctx -> {
Exception error = ctx.exception();
if (error instanceof ValidationException) {
// Fix and retry
ctx.agenticScope().writeState("fixed", true);
return ErrorRecoveryResult.retry();
} else if (error instanceof RateLimitException) {
// Wait and retry
Thread.sleep(1000);
return ErrorRecoveryResult.retry();
} else {
// Give up
return ErrorRecoveryResult.result(
Map.of("error", error.getMessage())
);
}
})
.build();UntypedAgent backoffAgent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.errorHandler(ctx -> {
AgenticScope scope = ctx.agenticScope();
int attempt = scope.readState("attempt", 0);
if (attempt < 5) {
scope.writeState("attempt", attempt + 1);
// Exponential backoff
long backoffMs = (long) Math.pow(2, attempt) * 1000;
Thread.sleep(backoffMs);
return ErrorRecoveryResult.retry();
}
return ErrorRecoveryResult.throwException();
})
.build();class CircuitBreaker implements Function<ErrorContext, ErrorRecoveryResult> {
private int failureCount = 0;
private boolean circuitOpen = false;
private static final int THRESHOLD = 5;
@Override
public ErrorRecoveryResult apply(ErrorContext ctx) {
if (circuitOpen) {
return ErrorRecoveryResult.result("Circuit breaker open");
}
failureCount++;
if (failureCount >= THRESHOLD) {
circuitOpen = true;
System.err.println("Circuit breaker opened");
}
return ErrorRecoveryResult.throwException();
}
public void reset() {
failureCount = 0;
circuitOpen = false;
}
}
UntypedAgent protectedAgent = AgenticServices.sequenceBuilder()
.subAgents(riskyAgent)
.errorHandler(new CircuitBreaker())
.build();.errorHandler(ctx -> {
Throwable error = ctx.exception();
// Transient errors - retry
if (error instanceof TimeoutException ||
error instanceof IOException ||
error instanceof ServiceUnavailableException) {
int retries = ctx.agenticScope().readState("retries", 0);
if (retries < 3) {
ctx.agenticScope().writeState("retries", retries + 1);
return ErrorRecoveryResult.retry();
}
}
// Business errors - return result with error info
if (error instanceof ValidationException ||
error instanceof BusinessRuleException) {
return ErrorRecoveryResult.result(
Map.of("status", "error", "message", error.getMessage())
);
}
// Fatal errors - propagate
return ErrorRecoveryResult.throwException();
})UntypedAgent parallelAgent = AgenticServices.parallelBuilder()
.subAgents(agent1, agent2, agent3)
.beforeCall(scope -> {
scope.writeState("errors", new ArrayList<String>());
})
.errorHandler(ctx -> {
List<String> errors = (List<String>) ctx.agenticScope().readState("errors");
errors.add(ctx.agentName() + ": " + ctx.exception().getMessage());
// Continue despite errors
return ErrorRecoveryResult.result(null);
})
.output(scope -> {
List<String> errors = (List<String>) scope.readState("errors");
return Map.of(
"results", scope.state(),
"errors", errors,
"success", errors.isEmpty()
);
})
.build();.errorHandler(ctx -> {
AgenticScope scope = ctx.agenticScope();
// Record what succeeded before failure
List<String> completed = (List<String>) scope.readState("completed_steps");
// Execute compensating actions in reverse order
for (int i = completed.size() - 1; i >= 0; i--) {
String step = completed.get(i);
rollback(step);
}
// Return partial success info
return ErrorRecoveryResult.result(
Map.of(
"status", "rolled_back",
"completed", completed,
"failed_at", ctx.agentName()
)
);
})UntypedAgent loop = AgenticServices.loopBuilder()
.subAgents(processor)
.maxIterations(10)
.loopCounterName("attempt")
.errorHandler(ctx -> {
AgenticScope scope = ctx.agenticScope();
Integer attempt = (Integer) scope.readState("attempt");
// Log error
System.err.println("Attempt " + attempt + " failed");
// Track error history
List<String> errorHistory = scope.readState("error_history", new ArrayList<>());
errorHistory.add(ctx.exception().getMessage());
scope.writeState("error_history", errorHistory);
// Continue loop (retry)
return ErrorRecoveryResult.retry();
})
.exitCondition(scope -> {
// Exit if too many consecutive errors
List<String> errors = scope.readState("error_history", new ArrayList<>());
return errors.size() >= 5;
})
.build();Example:
interface RobustSystem {
@SequenceAgent(
name = "robust-pipeline",
subAgents = {Stage1.class, Stage2.class, Stage3.class}
)
String process(String input);
@ErrorHandler
ErrorRecoveryResult handleError(Throwable error, AgenticScope scope) {
// Track error count
int errorCount = scope.readState("error_count", 0);
scope.writeState("error_count", errorCount + 1);
// Retry transient errors
if (error instanceof TransientException && errorCount < 3) {
return ErrorRecoveryResult.retry();
}
// Return partial results for business errors
if (error instanceof BusinessException) {
return ErrorRecoveryResult.result(
scope.readState("last_successful_result")
);
}
// Propagate unexpected errors
return ErrorRecoveryResult.throwException();
}
}.errorHandler(ctx -> {
// Comprehensive error logging
System.err.println("=== Error Report ===");
System.err.println("Agent: " + ctx.agentName());
System.err.println("Error Type: " + ctx.exception().getClass().getName());
System.err.println("Message: " + ctx.exception().getMessage());
System.err.println("Current State:");
ctx.agenticScope().state().forEach((key, value) -> {
System.err.println(" " + key + ": " + value);
});
System.err.println("Invocation History:");
ctx.agenticScope().agentInvocations().forEach(invocation -> {
System.err.println(" " + invocation.agentName() +
" -> " + invocation.result());
});
System.err.println("====================");
return ErrorRecoveryResult.throwException();
})UntypedAgent timeoutAgent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.errorHandler(ctx -> {
if (ctx.exception() instanceof TimeoutException) {
AgenticScope scope = ctx.agenticScope();
int timeoutCount = scope.readState("timeout_count", 0);
if (timeoutCount < 2) {
scope.writeState("timeout_count", timeoutCount + 1);
// Increase timeout and retry
return ErrorRecoveryResult.retry();
} else {
// Switch to fallback
return ErrorRecoveryResult.result("timeout_fallback");
}
}
return ErrorRecoveryResult.throwException();
})
.build();.errorHandler(ctx -> {
AgenticScope scope = ctx.agenticScope();
try {
// Cleanup resources
Connection conn = (Connection) scope.readState("db_connection");
if (conn != null && !conn.isClosed()) {
conn.rollback();
conn.close();
}
FileHandle file = (FileHandle) scope.readState("temp_file");
if (file != null) {
file.delete();
}
} catch (Exception cleanupError) {
System.err.println("Cleanup failed: " + cleanupError.getMessage());
}
return ErrorRecoveryResult.throwException();
})class MetricTrackingErrorHandler implements Function<ErrorContext, ErrorRecoveryResult> {
private final MetricsCollector metrics;
@Override
public ErrorRecoveryResult apply(ErrorContext ctx) {
// Record error metric
metrics.recordError(
ctx.agentName(),
ctx.exception().getClass().getSimpleName()
);
// Track error rate
double errorRate = metrics.getErrorRate(ctx.agentName());
if (errorRate > 0.5) {
// High error rate - circuit break
return ErrorRecoveryResult.result("Service degraded");
}
// Retry with backoff
int attempt = ctx.agenticScope().readState("attempt", 0);
if (attempt < 3) {
ctx.agenticScope().writeState("attempt", attempt + 1);
return ErrorRecoveryResult.retry();
}
return ErrorRecoveryResult.throwException();
}
}Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-agentic@1.11.0docs
declarative
A2AClientAgent
ActivationCondition
Agent
ConditionalAgent
ErrorHandler
ExitCondition
HumanInTheLoop
HumanInTheLoopResponseSupplier
LoopAgent
LoopCounter
Output
ParallelAgent
ParallelExecutor
PlannerAgent
SequenceAgent
SupervisorAgent
SupervisorRequest
quick-start
workflows