Implementing LangGraph4j real world use case
Here is the java code showing the real world use case of using LangGraph in Java applications
package ai;
import org.bsc.langgraph4j.*;
import org.bsc.langgraph4j.checkpoint.MemorySaver;
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.StateSnapshot;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.utils.CollectionsUtils.mapOf;
/**
* Simple LangGraph4j Example: Payment Approval Workflow with Checkpointing
*
* This demonstrates:
* - Using plain HashMaps (no custom state classes)
* - Conditional routing
* - Real checkpointing with pause/resume using thread_id
* - Human-in-the-loop approval pattern
*/
public class VerySimpleLangGraphExample {
// Use interrupt() to pause the workflow at this node
private static final String APPROVAL_NODE = "wait_approval";
/**
* Node 1: Validate the payment request
*/
private static Map<String, Object> validatePayment(AgentState state) {
int amount = (int) state.data().getOrDefault("amount", 0);
String vendor = (String) state.data().getOrDefault("vendor", "Unknown");
System.out.println("\n[VALIDATE] Checking payment request:");
System.out.println(" Amount: $" + amount);
System.out.println(" Vendor: " + vendor);
// Basic validation
boolean valid = amount > 0 && amount <= 100000;
Map<String, Object> updates = new HashMap<>();
updates.put("valid", valid);
updates.put("status", valid ? "validated" : "rejected");
if (!valid) {
updates.put("reason", "Amount must be between $1 and $100,000");
}
System.out.println(" Result: " + (valid ? "✓ Valid" : "✗ Invalid"));
return updates;
}
/**
* Node 2: Auto-approve small payments
*/
private static Map<String, Object> autoApprove(AgentState state) {
int amount = (int) state.data().getOrDefault("amount", 0);
System.out.println("\n[AUTO-APPROVE] Processing small payment: $" + amount);
Map<String, Object> updates = new HashMap<>();
updates.put("approved", true);
updates.put("approver", "System");
updates.put("status", "auto_approved");
System.out.println(" Result: ✓ Auto-approved (under $1000)");
return updates;
}
/**
* Node 3: Wait for manager approval
*/
private static Map<String, Object> waitForApproval(AgentState state) {
int amount = (int) state.data().getOrDefault("amount", 0);
boolean approved = (boolean) state.data().getOrDefault("approved", false);
System.out.println("\n[APPROVAL] Large payment requires manager approval:");
System.out.println(" Amount: $" + amount);
System.out.println(" Status: " + (approved ? "Approved by manager" : "Waiting..."));
Map<String, Object> updates = new HashMap<>();
if (!approved) {
updates.put("status", "pending_approval");
System.out.println(" Action: Workflow paused, waiting for manager");
} else {
updates.put("status", "approved");
updates.put("approver", "Manager");
System.out.println(" Result: ✓ Approved by manager");
}
return updates;
}
/**
* Node 4: Process the payment
*/
private static Map<String, Object> processPayment(AgentState state) {
int amount = (int) state.data().getOrDefault("amount", 0);
String vendor = (String) state.data().getOrDefault("vendor", "Unknown");
String approver = (String) state.data().getOrDefault("approver", "Unknown");
System.out.println("\n[PROCESS] Processing payment:");
System.out.println(" Amount: $" + amount);
System.out.println(" Vendor: " + vendor);
System.out.println(" Approved by: " + approver);
// Simulate payment processing
String transactionId = "TXN-" + System.currentTimeMillis();
Map<String, Object> updates = new HashMap<>();
updates.put("status", "completed");
updates.put("transactionId", transactionId);
updates.put("processed", true);
System.out.println(" Result: ✓ Payment processed successfully");
System.out.println(" Transaction ID: " + transactionId);
return updates;
}
/**
* Node 5: Reject payment
*/
private static Map<String, Object> rejectPayment(AgentState state) {
String reason = (String) state.data().getOrDefault("reason", "Invalid request");
System.out.println("\n[REJECT] Payment rejected:");
System.out.println(" Reason: " + reason);
Map<String, Object> updates = new HashMap<>();
updates.put("status", "rejected");
updates.put("processed", false);
return updates;
}
/**
* Routing: Check if payment is valid
*/
private static String routeAfterValidation(AgentState state) {
boolean valid = (boolean) state.data().getOrDefault("valid", false);
return valid ? "check_amount" : "reject";
}
/**
* Routing: Check if payment needs approval
*/
private static String routeByAmount(AgentState state) {
int amount = (int) state.data().getOrDefault("amount", 0);
boolean approved = (boolean) state.data().getOrDefault("approved", false);
if (amount < 1000) {
return "auto_approve";
} else {
// If already approved, go to process, else wait for approval
return approved ? "process" : "wait_approval";
}
}
/**
* Build the payment approval workflow
*/
public static CompiledGraph<AgentState> buildPaymentWorkflow() throws Exception {
StateGraph<AgentState> workflow = new StateGraph<>(AgentState::new);
// Add all nodes
workflow.addNode("validate", state -> CompletableFuture.completedFuture(validatePayment(state)));
workflow.addNode("auto_approve", state -> CompletableFuture.completedFuture(autoApprove(state)));
workflow.addNode("wait_approval", state -> CompletableFuture.completedFuture(waitForApproval(state)));
workflow.addNode("process", state -> CompletableFuture.completedFuture(processPayment(state)));
workflow.addNode("reject", state -> CompletableFuture.completedFuture(rejectPayment(state)));
// Define workflow edges
workflow.addEdge(START, "validate");
// After validation: go to check_amount or reject
workflow.addConditionalEdges(
"validate",
state -> CompletableFuture.completedFuture(routeAfterValidation(state)),
Map.of(
"check_amount", "check_amount",
"reject", "reject"
)
);
// Dummy node for routing
workflow.addNode("check_amount", state -> CompletableFuture.completedFuture(new HashMap<>()));
// Route by amount
workflow.addConditionalEdges(
"check_amount",
state -> CompletableFuture.completedFuture(routeByAmount(state)),
Map.of(
"auto_approve", "auto_approve",
"wait_approval", "wait_approval",
"process", "process"
)
);
// After wait_approval: always go to process
// (we interrupt after wait_approval, so it only continues when approved)
workflow.addEdge("wait_approval", "process");
// Terminal nodes
workflow.addEdge("auto_approve", "process");
workflow.addEdge("process", END);
workflow.addEdge("reject", END);
// Compile WITH MemorySaver for checkpointing
// Use interruptAfter to pause AFTER wait_approval node
// This allows the node to execute and show "Waiting..." status
CompileConfig config = CompileConfig.builder()
.checkpointSaver(new MemorySaver())
.interruptAfter("wait_approval") // Pause AFTER executing this node
.build();
return workflow.compile(config);
}
public static void main(String[] args) {
try {
System.out.println("╔════════════════════════════════════════════════════════════╗");
System.out.println("║ Payment Approval Workflow with Checkpointing ║");
System.out.println("╚════════════════════════════════════════════════════════════╝");
CompiledGraph<AgentState> app = buildPaymentWorkflow();
List<NodeOutput<AgentState>> outputs = new ArrayList<>();
/**
// Scenario 1: Small payment (auto-approved)
System.out.println("\n" + "=".repeat(70));
System.out.println("SCENARIO 1: Small Payment ($500) - Should Auto-Approve");
System.out.println("=".repeat(70));
Map<String, Object> smallPayment = new HashMap<>();
smallPayment.put("amount", 500);
smallPayment.put("vendor", "Office Supplies Inc");
smallPayment.put("approved", false);
app.stream(smallPayment).forEach(outputs::add);
if (!outputs.isEmpty()) {
AgentState finalState = outputs.get(outputs.size() - 1).state();
System.out.println("\n" + "-".repeat(70));
System.out.println("FINAL STATUS: " + finalState.data().get("status"));
System.out.println("Transaction ID: " + finalState.data().get("transactionId"));
System.out.println("-".repeat(70));
}
**/
// Scenario 2: Large payment with REAL checkpointing (pause/resume)
System.out.println("\n\n" + "=".repeat(70));
System.out.println("SCENARIO 2: Large Payment ($5000) - With Checkpointing");
System.out.println("=".repeat(70));
System.out.println("\n--- Step 1: Submit request (workflow will pause) ---");
// Create initial request
Map<String, Object> largePayment = new HashMap<>();
largePayment.put("amount", 5000);
largePayment.put("vendor", "Enterprise Software Corp");
largePayment.put("approved", false);
// IMPORTANT: Use thread_id for checkpointing
String threadId = "payment_request_001";
RunnableConfig config = RunnableConfig.builder()
.threadId(threadId)
.build();
outputs.clear();
app.stream(largePayment, config).forEach(output -> {
outputs.add(output);
System.out.println(" Executed node: " + output.node());
});
if (!outputs.isEmpty()) {
AgentState pausedState = outputs.get(outputs.size() - 1).state();
System.out.println("\n" + "-".repeat(70));
System.out.println("STATUS: " + pausedState.data().get("status"));
System.out.println("✋ Workflow PAUSED at approval step");
System.out.println("State saved with thread_id: " + threadId);
System.out.println("-".repeat(70));
}
System.out.println("\n--- Simulating time passing... Manager reviews request ---");
Thread.sleep(1000);
System.out.println("\n--- Step 2: Manager approves, RESUME from checkpoint ---");
// Get the current state from checkpoint
StateSnapshot<AgentState> snapshot = app.getState(config);
// Update the approval in the current state
Map<String, Object> updates = new HashMap<>();
updates.put("approved", true);
// Update the state at "wait_approval" node (where it paused)
app.updateState(config, updates, "wait_approval");
// Now stream with null to continue from where it paused
// It will execute process node and complete
outputs.clear();
app.stream(null, config).forEach(output -> {
outputs.add(output);
System.out.println(" Executed node: " + output.node());
});
if (!outputs.isEmpty()) {
AgentState finalState = outputs.get(outputs.size() - 1).state();
System.out.println("\n" + "-".repeat(70));
System.out.println("FINAL STATUS: " + finalState.data().get("status"));
System.out.println("Transaction ID: " + finalState.data().get("transactionId"));
System.out.println("✓ Workflow RESUMED and completed successfully!");
System.out.println("-".repeat(70));
}
// Scenario 3: Invalid payment
System.out.println("\n\n" + "=".repeat(70));
System.out.println("SCENARIO 3: Invalid Payment ($0) - Should Be Rejected");
System.out.println("=".repeat(70));
Map<String, Object> invalidPayment = new HashMap<>();
invalidPayment.put("amount", 0);
invalidPayment.put("vendor", "Invalid Vendor");
invalidPayment.put("approved", false);
outputs.clear();
app.stream(invalidPayment).forEach(outputs::add);
if (!outputs.isEmpty()) {
AgentState finalState = outputs.get(outputs.size() - 1).state();
System.out.println("\n" + "-".repeat(70));
System.out.println("FINAL STATUS: " + finalState.data().get("status"));
System.out.println("Reason: " + finalState.data().get("reason"));
System.out.println("-".repeat(70));
}
System.out.println("\n\n" + "=".repeat(70));
System.out.println("✓ All scenarios completed successfully!");
System.out.println("=".repeat(70));
System.out.println("\nKey Features Demonstrated:");
System.out.println(" ✓ Plain HashMap state (no custom classes)");
System.out.println(" ✓ Conditional routing based on amount");
System.out.println(" ✓ Auto-approval for small payments");
System.out.println(" ✓ REAL checkpointing with pause/resume using thread_id");
System.out.println(" ✓ Human-in-the-loop for large payments");
System.out.println(" ✓ Validation and error handling");
System.out.println("\nHow Checkpointing Works:");
System.out.println(" 1. Compile workflow with MemorySaver");
System.out.println(" 2. First call: stream(data, config) with thread_id");
System.out.println(" 3. Workflow pauses, state saved automatically");
System.out.println(" 4. Second call: stream(updates, SAME thread_id)");
System.out.println(" 5. Workflow resumes from checkpoint!");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}