Java SDK inspired by Python langgraph_sdk for interacting with a LangGraph server.
Target alignment baseline:
- Python package:
langgraph_sdk - Version:
0.3.9
This directory is a separate Java SDK prototype and does not reuse clients.java.
The design goal is to align with the resource layout of Python langgraph_sdk,
while keeping Java-friendly factory naming:
Clients.getClient(...)-> sync- top-level client with:
threadsrunsassistantscronsstore
Current implementation focuses on the synchronous subset that is most relevant for non-reactive Java applications. It also includes synchronous Java adaptations of Python's server-side extension concepts:
AuthEncryptionServerRuntime
com.simpleagents.langgraphsdk
├── Clients
├── SyncLangGraphClient
├── auth
│ ├── Auth
│ ├── AuthContext
│ ├── AuthRequest
│ └── BaseUser / BasicUser
├── assistants
│ └── AssistantsClient
├── cron
│ └── CronClient
├── encryption
│ ├── Encryption
│ └── EncryptionContext
├── http
│ └── HttpClient
├── runtime
│ ├── AccessContext
│ └── ServerRuntime
├── runs
│ └── RunsClient
├── store
│ └── StoreClient
├── threads
│ └── ThreadsClient
└── schema
├── Assistant
├── AssistantSelectField
├── AssistantSortBy
├── AssistantVersion
├── AssistantsSearchResponse
├── BulkCancelRunsStatus
├── CancelAction
├── Checkpoint
├── Config
├── Cron
├── CronSelectField
├── CronSortBy
├── DisconnectMode
├── Durability
├── FileType
├── GraphSchema
├── IfNotExists
├── Interrupt
├── ListNamespaceResponse
├── MultitaskStrategy
├── OnCompletionBehavior
├── OnConflictBehavior
├── PruneStrategy
├── Run
├── RunSelectField
├── RunStatus
├── RunStreamOptions
├── SearchItem
├── SearchItemsResponse
├── SortOrder
├── StoreItem
├── StreamMode
├── StreamPart
├── Thread
├── ThreadSelectField
├── ThreadSortBy
├── ThreadState
├── ThreadStatus
├── ThreadStreamMode
├── ThreadTask
├── ThreadUpdateStateResponse
└── WireValue
Clients.getClient(url)Clients.getClient(url, apiKey)Clients.getSyncClient(url)Clients.getSyncClient(url, apiKey)
client.threads.get(threadId)client.threads.get(threadId, include, headers, params)client.threads.create(threadId, metadata)client.threads.create(..., graphId, ifExists, ttl)client.threads.create(..., graphId, ifExists, supersteps, ttl, headers, params)client.threads.update(threadId, metadata)client.threads.update(threadId, metadata, ttl, headers, params)client.threads.delete(threadId)client.threads.search(..., sortBy, sortOrder, select, extract, headers, params)client.threads.count(...)client.threads.copy(threadId)client.threads.prune(threadIds, strategy)client.threads.getState(...)client.threads.updateState(...)client.threads.getHistory(...)client.threads.joinStream(...)
client.runs.create(threadId, assistantId, options)client.runs.createBatch(payloads)client.runs.get(threadId, runId)client.runs.list(threadId, limit, offset)client.runs.list(..., status, select, headers, params)client.runs.cancel(threadId, runId)client.runs.cancel(..., wait, action, headers, params)client.runs.cancelMany(...)client.runs.join(threadId, runId)client.runs.stream(threadId, assistantId, options, onPart)client.runs.streamToList(threadId, assistantId, options)client.runs.joinStream(..., streamMode, cancelOnDisconnect, lastEventId, headers, params, onPart)client.runs.wait(threadId, assistantId, options)client.runs.delete(threadId, runId)
client.assistants.get(assistantId)client.assistants.getGraph(...)client.assistants.getSchemas(...)client.assistants.getSubgraphs(...)client.assistants.create(...)client.assistants.update(...)client.assistants.delete(..., deleteThreads, headers, params)client.assistants.search(...)client.assistants.searchResponse(...)client.assistants.count(...)client.assistants.getVersions(...)client.assistants.setLatest(...)
client.store.putItem(...)client.store.getItem(...)client.store.deleteItem(...)client.store.searchItems(...)client.store.searchItemsList(...)client.store.searchItemsResponse(...)client.store.listNamespaces(...)client.store.listNamespacesResponse(...)
Notes:
searchItems(...)returns typedSearchItemsResponse, aligned with Python sync SDK.searchItemsList(...)is a Java-friendly convenience method when only the item list is needed.
client.crons.createForThread(...)client.crons.create(...)client.crons.update(...)client.crons.delete(...)client.crons.search(...)client.crons.count(...)
Notes:
createForThread(...)andcreate(...)returnRun, matching Python sync SDK behavior.
new Auth()auth.authenticate(handler)auth.on.any(handler)auth.on.assistants.any(handler)auth.on.threads.read.register(handler)auth.on.threads.createRun.register(handler)auth.on.store.put.register(handler)auth.authorize(ctx, value)
new Encryption()encryption.encrypt.blob(handler)encryption.encrypt.json(handler)encryption.decrypt.blob(handler)encryption.decrypt.json(handler)encryption.context(handler)encryption.resolveContext(user, ctx)
ServerRuntime.execution(...)ServerRuntime.read(...)runtime.executionRuntime()runtime.ensureUser()
ConfigFileTypeRunStreamOptionsStreamPartAssistantRunAssistantVersionAssistantsSearchResponseThreadThreadStateThreadTaskThreadUpdateStateResponseCheckpointGraphSchemaStoreItemSearchItemSearchItemsResponseListNamespaceResponseCronInterrupt- core enums:
AssistantSortByAssistantSelectFieldStreamModeThreadSortByThreadSelectFieldThreadStreamModeCronSortByCronSelectFieldDisconnectModeMultitaskStrategyOnCompletionBehaviorOnConflictBehaviorPruneStrategyDurabilityIfNotExistsRunStatusThreadStatusSortOrder
See:
Main usage pattern:
SyncLangGraphClient client = Clients.getClient("http://localhost:2024");
client.threads.create("thread-1", Map.of("name", "example"));
RunStreamOptions options = new RunStreamOptions();
options.input = Map.of(
"messages",
List.of(
Map.of(
"type", "human",
"content", List.of(
Map.of("type", "text", "text", "你好")
)
)
)
);
options.ifNotExists = IfNotExists.create;
options.streamMode = List.of(StreamMode.messages_tuple);
client.runs.stream("thread-1", "research", options, part -> {
System.out.println(part);
});If you want message-chunk style streaming instead of state snapshots, explicitly pass:
options.streamMode = List.of(StreamMode.messages_tuple);Server-side extension pattern:
Auth auth = new Auth();
auth.authenticate(request -> new BasicUser("demo-user"));
auth.on.threads.read.register((ctx, value) -> AuthorizationDecision.allow());
auth.on.store.put.register((ctx, value) -> AuthorizationDecision.filter(
Map.of("owner", ctx.getUser().getIdentity())
));
Encryption encryption = new Encryption();
encryption.encrypt.json((ctx, data) -> Map.of("secret", "enc:" + data.get("secret")));
encryption.decrypt.json((ctx, data) -> Map.of("secret", String.valueOf(data.get("secret")).substring(4)));
ServerRuntime<Map<String, Object>> runtime = ServerRuntime.execution(
AccessContext.threads_create_run,
new BasicUser("demo-user"),
new Object(),
Map.of("tenant_id", "t-1")
);This SDK now uses typed schema models in a meaningful subset of the API.
Examples:
RunStreamOptions.streamModeusesList<StreamMode>ThreadsClient.search(..., sortBy, sortOrder, select, extract, ...)ThreadsClient.get(..., include, ...)RunStreamOptions.ifNotExistsusesIfNotExistsRunStreamOptions.multitaskStrategyusesMultitaskStrategyRunStreamOptions.onDisconnectusesDisconnectModeRunStreamOptions.onCompletionusesOnCompletionBehaviorRunStreamOptions.durabilityusesDurabilityRunsClient.list(..., status, select, ...)usesRunStatusandRunSelectFieldRunsClient.get/create/listreturn typedRunThreadsClient.get/create/update/searchreturn typedThreadThreadsClient.getState/getHistoryreturn typedThreadStateThreadsClient.updateStatereturns typedThreadUpdateStateResponseAssistantsClient.get/create/update/searchreturn typedAssistantAssistantsClient.searchResponsereturns typedAssistantsSearchResponseStoreClient.searchItemsreturns typedSearchItemsResponseStoreClient.listNamespacesResponsereturns typedListNamespaceResponse
Enums that differ from Java identifier rules use internal names plus wire values.
Examples:
StreamMode.messages_tuple->"messages-tuple"DisconnectMode.continue_->"continue"
The current file behavior follows the Python SDK wrapper style used in this repository:
- local file -> base64
file - URL -> passthrough
file_url
Example:
FileType local = FileType.fromPath("/tmp/demo.pdf");
FileType remote = FileType.fromUrl("https://example.com/demo.pdf");This SDK is still a partial alignment, not a full 1:1 port of Python langgraph_sdk.
Main remaining gaps:
- Java
Auth/Encryptionuse synchronous handlers, while Python uses async handlers auth.typesis not modeled 1:1 as dozens of Java DTOs; authorization payloads remain generic- full schema parity with Python
langgraph_sdk.schema - a few advanced overloads and less-common response shapes are still compressed into Java-friendly forms
- more exhaustive boundary and combination tests
cd langgraph-java-sdk
mvn -q clean test