Oracle Checkpoint Saver V1
Version 1 is implemented by org.bsc.langgraph4j.checkpoint.OracleSaver. It stores each LangGraph4j thread in LANGRAPH4J_THREAD and stores the thread's checkpoint history in LANGRAPH4J_CHECKPOINT.
V1 is useful for compatibility with the original Oracle saver schema. New applications should usually start with SAVER_V2.md.
Data Architecture
The schema is defined in src/main/resources/db/migration/v1.1__init.sql.
erDiagram
LANGRAPH4J_THREAD {
VARCHAR2 thread_id PK
VARCHAR2 thread_name
BOOLEAN is_released
}
LANGRAPH4J_CHECKPOINT {
VARCHAR2 checkpoint_id PK
VARCHAR2 thread_id FK
VARCHAR2 node_id
VARCHAR2 next_node_id
CLOB state_data
VARCHAR2 state_content_type
TIMESTAMP saved_at
}
LANGRAPH4J_THREAD ||--o{ LANGRAPH4J_CHECKPOINT : owns
IDX_LANGRAPH4J_THREAD_NAME_RELEASED supports finding the active row for a thread name.
Design
OracleSaver stores a logical LangGraph4j thread name in LANGRAPH4J_THREAD.thread_name. Each new thread row has a UUID thread_id, and checkpoints reference that id.
When a checkpoint is saved, the saver:
- Merges an unreleased
LANGRAPH4J_THREADrow for the configured thread name when one does not already exist. - Inserts a
LANGRAPH4J_CHECKPOINTrow for the active thread with the checkpoint id, node ids, serialized state payload, and serializer content type.
State is serialized through the first configured StateSerializer. The binary serializer output is Base64 encoded and stored in LANGRAPH4J_CHECKPOINT.state_data as a CLOB.
The state_content_type column records the serializer content type. On read, the saver uses that content type to select a matching registered serializer.
Release Behavior
Releasing a thread sets LANGRAPH4J_THREAD.is_released to TRUE. The checkpoint rows remain in LANGRAPH4J_CHECKPOINT, but normal V1 checkpoint loading searches only unreleased thread rows.
OracleSaver.tag(config, version) returns Optional.empty() in V1. Use V2 when you need versioned release history.
Limitations
- No release tag table and no versioned lookup for released checkpoint histories.
- No persistent interruption state.
registerInterruption(...)completes without changing the database. - The schema does not declare
thread_nameunique; applications should avoid concurrent active executions with the same logical thread name. - V1's current implementation uses the V1.1 schema, which adds
state_content_type; the older V1.0 resource is retained only for legacy schema compatibility.
Build a Saver
import oracle.jdbc.datasource.OracleDataSource;
import org.bsc.langgraph4j.checkpoint.CreateOption;
import org.bsc.langgraph4j.checkpoint.OracleSaver;
import org.bsc.langgraph4j.serializer.std.ObjectStreamStateSerializer;
import org.bsc.langgraph4j.state.AgentState;
var dataSource = new OracleDataSource();
dataSource.setURL("jdbc:oracle:thin:@localhost:1521/FREEPDB1?oracle.jdbc.provider.json=jackson-json-provider");
dataSource.setUser("app_user");
dataSource.setPassword("app_password");
var saver = OracleSaver.builder()
.dataSource(dataSource)
.stateSerializer(new ObjectStreamStateSerializer<>(AgentState::new))
.createOption(CreateOption.CREATE_IF_NOT_EXISTS)
.build();
Use the saver when compiling a graph:
var compileConfig = CompileConfig.builder()
.checkpointSaver(saver)
.releaseThread(false)
.build();
var workflow = graph.compile(compileConfig);