# Java Port Implementation Plan for LangGraph This document outlines the plan for implementing a Java port of LangGraph, following the specifications defined in the other documents. ## Project Structure ``` langgraph-java/ ├── build.gradle ├── settings.gradle ├── README.md ├── langgraph-checkpoint/ # Base persistence interfaces (equiv. to libs/checkpoint) │ └── src/ │ ├── main/java/ │ │ └── com/langgraph/checkpoint/ │ │ ├── base/ # Base interfaces and utilities │ │ └── serde/ # Serialization │ └── test/java/ ├── langgraph-core/ # Main library (equiv. to libs/langgraph) │ └── src/ │ ├── main/java/ │ │ └── com/langgraph/ │ │ ├── channels/ # Channel implementations │ │ ├── graph/ # StateGraph API │ │ └── pregel/ # Pregel execution engine │ └── test/java/ ├── langgraph-examples/ # Example applications │ └── src/ │ └── main/java/ │ └── com/langgraph/examples/ └── langgraph-checkpoint-postgres/ # Postgres implementation (future) ``` ## Development Phases The implementation will follow a bottom-up approach, starting with the lowest-level components and building up to the high-level APIs. ### Phase 1: Foundation (langgraph-checkpoint) 1. **Set up project structure** - Initialize Gradle project - Configure dependencies - Set up testing framework (JUnit 5) 2. **Implement ID utilities** - `ID.java` - For deterministic ID generation - Test with different inputs 3. **Implement serialization framework** - `Serializer` interface - `ReflectionSerializer` interface - `TypeSerializer` and `TypeDeserializer` interfaces - `MsgPackSerializer` implementation - Comprehensive tests for serialization/deserialization 4. **Implement checkpoint interfaces** - `BaseCheckpointSaver` interface - `AsyncBaseCheckpointSaver` interface - `MemoryCheckpointSaver` implementation - Tests for checkpoint operations ### Phase 2: State Management (langgraph-core) 1. **Implement channel system** - `Channel` interface - Basic channel implementations: - `LastValue` - `AnyValue` - `EphemeralValue` - `UntrackedValue` - Advanced channel implementations: - `Topic` - `BinaryOperatorAggregate` - `NamedBarrierValue` - Tests for each channel type 2. **Implement schema validation** - `SchemaValidator` interface - `RecordSchemaValidator` implementation - Tests for schema validation ### Phase 3: Execution Engine (langgraph-core) 1. **Implement core Pregel interfaces** - `PregelProtocol` interface - `StreamMode` enum - `PregelExecutable` interface - `PregelNode` class - `PregelTask` classes - Tests for interfaces 2. **Implement execution system** - `RetryPolicy` interface - `Checkpoint` class - `Pregel` implementation - Tests for execution ### Phase 4: High-Level API (langgraph-core) 1. **Implement StateGraph** - `GraphConstants` constants - `NodeAction` and `EdgeCondition` interfaces - `StateGraph` class - `CompiledStateGraph` class - Tests for graph construction and execution 2. **Build examples** - Counter example - Conversation example - Multi-step workflow example ### Phase 5: Extensions (future) 1. **Implement Postgres adaptors** - PostgresCheckpointSaver - Tests for Postgres integration ## Testing Strategy Each component will be implemented following Test-Driven Development (TDD): 1. Write test cases based on Python implementation 2. Implement interface to satisfy tests 3. Implement concrete class(es) 4. Run tests and refine implementation 5. Document completed component ### Key Test Areas - **Serialization**: Test with various object types, nested structures, and cycles - **Channels**: Test all channel behaviors and edge cases - **Pregel**: Test deterministic execution, error handling, and checkpoint integration - **StateGraph**: Test graph construction, validation, and execution ## Implementation Notes ### Java-Specific Adaptations 1. **Records for state schemas** - Java Records (Java 14+) as TypedDict/Pydantic alternative - Reflection-based validation and conversion 2. **CompletableFuture for async** - Async interfaces use CompletableFuture - Parallel execution with work-stealing pools 3. **Generics for type safety** - Extensive use of generics for type safety - Runtime type checking for dynamic aspects 4. **Builder pattern** - Builder pattern for complex object construction - Fluent interfaces for API usability ### Performance Considerations - Thread-safe implementations using concurrent collections - Atomic operations for parallel safety - Minimizing object creation and copying - Efficient serialization with MessagePack ## Initial Implementation Tasks 1. Set up project structure and build system 2. Implement ID utilities in checkpoint module 3. Implement MsgPackSerializer 4. Implement MemoryCheckpointSaver 5. Write comprehensive tests for foundation layer ## Timeline 1. **Phase 1: Foundation** - 2 weeks 2. **Phase 2: State Management** - 2 weeks 3. **Phase 3: Execution Engine** - 3 weeks 4. **Phase 4: High-Level API** - 2 weeks 5. **Integration and Examples** - 1 week Total estimated time: ~10 weeks for core functionality ## Dependencies - **MessagePack**: `org.msgpack:msgpack-core:0.9.3` - **JUnit 5**: `org.junit.jupiter:junit-jupiter:5.8.2` - **Mockito**: `org.mockito:mockito-core:4.5.1` - **AssertJ**: `org.assertj:assertj-core:3.22.0` ## Phase 3 in detail: This implementation plan is organized to match the structure of the Python implementation while prioritizing testability at each step. Don't forget to use Java patterns and idioms where appropriate. 1. Core Data Structures & Interfaces (Week 1) Day 1-2: Base Interfaces and Types 1. StreamMode Enum - Implementation: Define values (VALUES, UPDATES, DEBUG) - Test: Verify serialization and string representation 2. PregelExecutable Interface - Implementation: Define execute method - Test: Create mock implementations for testing 3. RetryPolicy Interface & Implementations - Implementation: RetryPolicy interface with factory methods - Test: Verify retry decision logic Day 3-4: Task Management 1. PregelTask Class - Implementation: Node name, trigger, retry policy - Test: Constructor, getters, equals, hashCode 2. PregelExecutableTask Class - Implementation: Task with inputs and context - Test: Construction, input/context immutability Day 5: Core Protocol 1. PregelProtocol Interface - Implementation: Define methods from spec - Test: Mock implementation for testing 2. Node & Channel Integration (Week 2) Day 1-2: Node Implementation 1. PregelNode Class - Implementation: Full implementation with accessors - Test: Subscription, trigger, write permissions 2. NodeRegistry - Implementation: Managing collections of nodes - Test: Registration, lookup, validation Day 3-5: Message Handling 1. Checkpoint Class - Implementation: State snapshot storage - Test: Capture and restore state 2. ChannelRegistry - Implementation: Managing channel collections - Test: Registration, lookup, validation 3. Core Execution Components (Week 3) Day 1-2: Task Planning 1. PregelTaskPlanner - Implementation: Determine tasks to execute based on updates - Test: Task selection with different update patterns 2. TaskPrioritizer - Implementation: Order tasks for execution - Test: Priority ordering with different dependency patterns Day 3-5: Task Execution 1. TaskExecutor - Implementation: Execute tasks with retry logic - Test: Successful execution, error handling, retry behavior 2. ExecutionContext - Implementation: Thread-local context for execution - Test: Context propagation, thread safety 3. Superstep Management (Week 4) Day 1-2: Superstep Core 1. SuperstepManager - Implementation: Manage a single superstep execution - Test: Plan, execute, update phases with mock nodes 2. UpdateCollector - Implementation: Collect and apply channel updates - Test: Update ordering, conflict resolution Day 3-5: Execution Loop 1. PregelLoop - Implementation: Core execution logic, step iteration - Test: Loop termination, state tracking 2. CheckpointManager - Implementation: Integration with checkpointing - Test: Checkpoint captures, restore behavior 3. Full Engine & Streaming (Week 5) Day 1-3: Pregel Engine 1. Pregel Class - Implementation: Core engine with all components - Test: End-to-end execution, configuration 2. PregelBuilder - Implementation: Fluent builder interface - Test: Configuration options, validation Day 4-5: Streaming Support 1. StreamOutput - Implementation: Format output for streaming - Test: Different stream modes 2. StreamController - Implementation: Manage streaming state - Test: Backpressure, cancellation Implementation Strategy 1. Incremental Testing Create test classes for each component that can be used in isolation: ```java @Test void testTaskPlanning() { // Create mock channels with updates Map channels = createMockChannels( Map.of("input", true, "other", false)); // Create nodes with subscriptions Set nodes = createTestNodes(); // Create planner PregelTaskPlanner planner = new PregelTaskPlanner(nodes); // Test planning logic List tasks = planner.plan(channels); // Verify correct tasks selected assertThat(tasks).hasSize(1); assertThat(tasks.get(0).getNode()).isEqualTo("processor"); } ``` 2. Test Each Component in Isolation For each component, test: - Normal operation - Edge cases - Error conditions - Integration with dependencies ```java @Test void testTaskExecution() { // Create mock executor TaskExecutor executor = new TaskExecutor(); // Create task with expected inputs PregelExecutableTask task = createTestTask(); // Execute and capture results Map result = executor.execute(task); // Verify results assertThat(result) .containsKey("output") .containsEntry("output", "processed"); } ``` 3. Incremental Integration 1. Start with simplest components: PregelTask, StreamMode, etc. 1. Build TaskPlanner with mocked nodes 1. Create TaskExecutor with mocked actions 1. Integrate into SuperstepManager 1. Combine in PregelLoop 1. Build complete Pregel engine ```java // First, test task planner alone @Test void testTaskPlannerInIsolation() { PregelTaskPlanner planner = new PregelTaskPlanner(mockNodes); List tasks = planner.plan(updatedChannels); // Verify tasks } // Then, test executor alone @Test void testTaskExecutorInIsolation() { TaskExecutor executor = new TaskExecutor(); Map result = executor.execute(mockTask); // Verify result } // Finally, test them together in SuperstepManager @Test void testSuperstepIntegration() { SuperstepManager manager = new SuperstepManager( planner, executor, channels ); SuperstepResult result = manager.executeStep(); // Verify complete superstep behavior } ``` 4. Use Real Components When Possible 1. Use real Channel implementations from previous phase 1. Create simple test PregelExecutables 1. Build test workflows of increasing complexity ```java @Test void testSimpleWorkflow() { // Create real channels Map channels = new HashMap<>(); channels.put("input", new LastValue<>(String.class)); channels.put("output", new LastValue<>(String.class)); // Create real nodes Map nodes = new HashMap<>(); nodes.put("processor", new PregelNode( "processor", (inputs, context) -> { String input = (String) inputs.get("input"); return Map.of("output", input.toUpperCase()); }, Set.of("input"), null, Set.of("output"), null )); // Create real Pregel instance Pregel pregel = new Pregel(nodes, channels, null); // Run and verify Object result = pregel.invoke(Map.of("input", "hello"), null); // Verify complete execution @SuppressWarnings("unchecked") Map resultMap = (Map) result; assertThat(resultMap).containsEntry("output", "HELLO"); } ``` File Organization Based on the Python structure, here's how the Java implementation will be organized: com.langgraph.pregel/ ├── PregelProtocol.java # Core interface ├── StreamMode.java # Enum for streaming options ├── PregelExecutable.java # Interface for node functions ├── PregelNode.java # Node definition ├── task/ │ ├── PregelTask.java # Task representation │ ├── PregelExecutableTask.java # Task with inputs │ ├── TaskPlanner.java # Task planning logic │ └── TaskExecutor.java # Task execution ├── state/ │ ├── Checkpoint.java # State checkpoint │ ├── ChannelRegistry.java # Channel management │ └── NodeRegistry.java # Node management ├── execute/ │ ├── SuperstepManager.java # Single superstep execution │ ├── PregelLoop.java # Main execution loop │ ├── ExecutionContext.java # Context for execution │ └── UpdateCollector.java # Collect updates ├── stream/ │ ├── StreamController.java # Manage streaming │ └── StreamOutput.java # Format output ├── retry/ │ ├── RetryPolicy.java # Retry interface │ └── RetryPolicies.java # Standard policies └── Pregel.java # Main implementation Testing Step-by-Step The testing strategy follows a specific progression: 1. Unit Testing: Test each component in isolation 2. Component Testing: Test related components together 3. Integration Testing: Test main subsystems 4. System Testing: Test complete workflows Example Test Progression for TaskPlanner: 1. Unit Test: Mock everything ```java @Test void testPlannerWithMocks() { Set updatedChannels = Set.of("input"); Map mockNodes = createMockNodes(); TaskPlanner planner = new TaskPlanner(mockNodes); List tasks = planner.plan(updatedChannels); // Test with various update patterns } ``` 2. Component Test: Use real nodes, mock channels ```java @Test void testPlannerWithRealNodes() { Set updatedChannels = Set.of("input"); Map realNodes = createRealNodes(); TaskPlanner planner = new TaskPlanner(realNodes); List tasks = planner.plan(updatedChannels); // Verify with real node behavior } ``` 3. Integration Test: Use real nodes and channels ```java @Test void testPlannerIntegration() { Map channels = createRealChannels(); // Update channels channels.get("input").update("test"); Map nodes = createRealNodes(); // Get updated channel names Set updatedChannels = getUpdatedChannelNames(channels); TaskPlanner planner = new TaskPlanner(nodes); List tasks = planner.plan(updatedChannels); // Verify end-to-end planning } ``` 4. System Test: Use in a full Pregel execution ```java @Test void testPlannerInFullSystem() { // Set up complete Pregel system Pregel pregel = createTestPregelSystem(); // Execute a workflow that will trigger planning pregel.invoke(Map.of("input", "test"), null); // Verify entire execution via output } ``` Sample Test Case Implementations To illustrate the TDD approach, here are key test cases for early components: 1. PregelTask ```java @Test void testPregelTask() { // Basic construction PregelTask task = new PregelTask("node1", "trigger1", RetryPolicy.noRetry()); assertThat(task.getNode()).isEqualTo("node1"); assertThat(task.getTrigger()).isEqualTo("trigger1"); assertThat(task.getRetryPolicy()).isNotNull(); // Equality PregelTask sameTask = new PregelTask("node1", "trigger1", RetryPolicy.maxAttempts(3)); PregelTask differentNode = new PregelTask("node2", "trigger1", RetryPolicy.noRetry()); PregelTask differentTrigger = new PregelTask("node1", "trigger2", RetryPolicy.noRetry()); assertThat(task).isEqualTo(sameTask); assertThat(task).isNotEqualTo(differentNode); assertThat(task).isNotEqualTo(differentTrigger); } ``` 2. PregelNode ```java @Test void testPregelNode() { // Create a simple action PregelExecutable action = (inputs, context) -> Map.of("output", "result"); // Basic construction PregelNode node = new PregelNode( "processor", action, Set.of("input1", "input2"), "trigger1", Set.of("output1", "output2"), RetryPolicy.maxAttempts(3) ); // Test properties assertThat(node.getName()).isEqualTo("processor"); assertThat(node.getAction()).isSameAs(action); assertThat(node.getSubscribe()).containsExactlyInAnyOrder("input1", "input2"); assertThat(node.getTrigger()).isEqualTo("trigger1"); assertThat(node.getWriters()).containsExactlyInAnyOrder("output1", "output2"); assertThat(node.getRetryPolicy()).isNotNull(); // Test helper methods assertThat(node.subscribesTo("input1")).isTrue(); assertThat(node.subscribesTo("input3")).isFalse(); assertThat(node.hasTrigger("trigger1")).isTrue(); assertThat(node.hasTrigger("trigger2")).isFalse(); assertThat(node.canWriteTo("output1")).isTrue(); assertThat(node.canWriteTo("output3")).isFalse(); } ``` 3. TaskPlanner ```java @Test void testTaskPlanner() { // Create nodes PregelNode node1 = new PregelNode( "node1", (inputs, context) -> Map.of(), Set.of("channel1"), null, Set.of("output1"), null ); PregelNode node2 = new PregelNode( "node2", (inputs, context) -> Map.of(), Set.of("channel2"), null, Set.of("output2"), null ); PregelNode node3 = new PregelNode( "node3", (inputs, context) -> Map.of(), null, "trigger1", Set.of("output3"), null ); Map nodes = Map.of( "node1", node1, "node2", node2, "node3", node3 ); TaskPlanner planner = new TaskPlanner(nodes); // Test with different updated channels Set update1 = Set.of("channel1"); List tasks1 = planner.plan(update1); assertThat(tasks1).hasSize(1); assertThat(tasks1.get(0).getNode()).isEqualTo("node1"); Set update2 = Set.of("channel1", "channel2"); List tasks2 = planner.plan(update2); assertThat(tasks2).hasSize(2); Set update3 = Set.of("trigger1"); List tasks3 = planner.plan(update3); assertThat(tasks3).hasSize(1); assertThat(tasks3.get(0).getNode()).isEqualTo("node3"); Set update4 = Set.of("channel3"); List tasks4 = planner.plan(update4); assertThat(tasks4).isEmpty(); } ```