OpenClaw is a personal AI assistant written in TypeScript (~83.7%) that runs on your own devices. It uses a hub-and-spoke architecture:
- Gateway — WebSocket server (port 18789) as the central control plane
- Agent Runtime — Orchestrates AI turns via LLM APIs (Anthropic, OpenAI, etc.)
- Channels — Connects to messaging platforms (WhatsApp, Telegram, Slack, Discord, Signal, iMessage, etc.)
- Tools — Exposes file I/O, exec, browser, memory, web search, etc. to agents
- Memory — Semantic search over workspace files via SQLite + vector embeddings
- Sessions — JSONL transcript persistence with per-user/channel isolation
- Configuration — Zod-validated JSON5 with hot-reload
- Sandbox — Docker-based isolation for tool execution
- Extensions — npm plugin model for additional channels
- CLI — Command-line interface (
openclaw onboard,openclaw gateway,openclaw agent, etc.) - Native Apps — macOS (Swift), iOS, Android companion apps
The full system is ~600+ source files. This plan targets the absolute minimum that compiles, runs, and demonstrates core functionality.
Important
We are NOT converting the entire project. The MVP targets the smallest subset that proves the architecture works end-to-end in Java.
| Subsystem | MVP Scope |
|---|---|
| Configuration | Load a JSON config file with basic schema validation |
| Gateway | WebSocket server on port 18789 with health check RPC |
| Agent Runtime | Extensible LlmProvider interface, Anthropic as default implementation |
| Sessions | In-memory session store with JSONL file persistence |
| CLI | openclaw-java gateway and openclaw-java send commands |
| Channel | Console channel (stdin/stdout) — zero external deps for testing |
- ❌ All messaging channels (WhatsApp, Telegram, Discord, Slack, Signal, iMessage, etc.)
- ❌ Memory/vector search system
- ❌ Sandbox/Docker isolation
- ❌ Tool system (file I/O, exec, browser, canvas, cron, etc.)
- ❌ Extension/plugin loader
- ❌ Hot-reload configuration
- ❌ Multi-agent routing
- ❌ Native apps (macOS/iOS/Android)
- ❌ WebChat / Control UI
- ❌ Voice Wake / Talk Mode
- ❌ Tailscale / remote access
- ❌ DM pairing / security model (use simple token auth)
- ❌ Media pipeline (images, audio, video)
| TypeScript | Java Equivalent |
|---|---|
| Node.js runtime | JDK 21+ (virtual threads) |
ws (WebSocket) |
Java-WebSocket or Jetty WebSocket |
| Zod (schema validation) | Jackson + manual validation |
| JSON5 config | Jackson with json5 module or Gson |
| Anthropic SDK | anthropic-sdk-java or raw HTTP via OkHttp |
| JSONL file I/O | Jackson ObjectMapper + BufferedWriter |
commander (CLI) |
picocli |
pino (logging) |
SLF4J + Logback |
vitest (testing) |
JUnit 5 + Mockito |
| pnpm / npm | Gradle (Kotlin DSL) |
Gradle project with dependencies:
com.fasterxml.jackson.core:jackson-databind(JSON parsing)info.picocli:picocli(CLI)org.java-websocket:Java-WebSocket(WebSocket server)com.squareup.okhttp3:okhttp(HTTP client for LLM APIs)ch.qos.logback:logback-classic(logging)org.junit.jupiter:junit-jupiter(testing)
Maps to: src/config/zod-schema.ts + src/config/config.ts
POJO with Jackson annotations representing the minimal config:
public class OpenClawConfig {
private GatewayConfig gateway; // port, bind, auth token
private AgentConfig agent; // provider ("anthropic"), API key, model, system prompt
}Maps to: src/config/io.ts + src/config/validation.ts
Loads ~/.openclaw-java/config.json, validates required fields, returns OpenClawConfig. Project lives at ~/github/openclaw-java/.
Maps to: src/gateway/server.ts
WebSocket server using Java-WebSocket library:
- Binds to
127.0.0.1:18789 - Accepts JSON-RPC style messages
- Implements:
gateway.health,gateway.status,agent.send - Token-based auth on connection
Maps to: src/gateway/protocol.ts
Defines the RPC message envelope:
public class RpcMessage {
String id;
String method;
JsonNode params;
}
public class RpcResponse {
String id;
JsonNode result;
RpcError error;
}Maps to: src/gateway/router.ts
Routes incoming RPC method calls to handler functions.
Maps to: src/agents/agent-pi.ts
Core agent turn execution:
- Receives a user message
- Constructs messages array (system prompt + session history + user message)
- Calls
LlmProviderfor a completion - Returns assistant response text
- Appends turn to session
Maps to: src/agents/system-prompt.ts
Reads IDENTITY.md from workspace directory, builds system prompt string. Falls back to a default prompt if file doesn't exist.
Extensible interface for LLM backends:
public interface LlmProvider {
String complete(List<Message> messages, String model);
String providerName();
}Maps to: Integration with pi-ai SDK
Default LlmProvider implementation using Anthropic Messages API:
POST https://api.anthropic.com/v1/messages- Supports model selection (default:
claude-sonnet-4-20250514) - Uses OkHttp for HTTP, Jackson for JSON
- Returns parsed response text
Tip
To add OpenAI or other providers later, just implement LlmProvider and register in config.
Maps to: src/config/sessions/store.ts
- In-memory
Map<String, Session>keyed by session ID - Each
Sessionholds aList<Message>(role + content) - Appends to JSONL file at
~/.openclaw-java/sessions/<sessionId>.jsonl - Loads existing JSONL on startup
Maps to: Session concept in src/config/sessions.ts
public class Session {
String id;
String channelType; // "telegram", "cli"
String userId;
List<Message> messages;
Instant createdAt;
Instant lastActiveAt;
}Simple record:
public record Message(String role, String content, Instant timestamp) {}Maps to: Channel interface pattern from src/channels/
public interface Channel {
void start();
void stop();
void sendMessage(String sessionId, String text);
String getChannelType();
}Stdin/stdout interactive channel for local testing:
- Reads user input from
System.inin a loop - Creates a single session (
console-default) - Calls
AgentExecutorfor a response - Prints assistant reply to
System.out - Runs on a daemon thread, exits on
quit/exit
Maps to: src/cli/program.ts
picocli-based CLI with subcommands:
gateway— starts the Gateway + Console channelsend --message "text"— sends a message to the agent via WebSocket RPC and prints response
Starts GatewayServer, loads config, initializes SessionStore, starts ConsoleChannel.
Maps to: src/cli/agent-cli.ts
Connects to Gateway WebSocket, sends agent.send RPC, prints response.
Maps to: src/index.ts
Entry point that delegates to OpenClawCli.
openclaw-java/
├── build.gradle.kts
├── settings.gradle.kts
├── src/
│ ├── main/
│ │ ├── java/ai/openclaw/
│ │ │ ├── Main.java
│ │ │ ├── config/
│ │ │ │ ├── OpenClawConfig.java
│ │ │ │ ├── GatewayConfig.java
│ │ │ │ ├── AgentConfig.java
│ │ │ │ └── ConfigLoader.java
│ │ │ ├── gateway/
│ │ │ │ ├── GatewayServer.java
│ │ │ │ ├── RpcProtocol.java
│ │ │ │ └── RpcRouter.java
│ │ │ ├── agent/
│ │ │ │ ├── AgentExecutor.java
│ │ │ │ ├── SystemPromptBuilder.java
│ │ │ │ ├── LlmProvider.java
│ │ │ │ └── AnthropicProvider.java
│ │ │ ├── session/
│ │ │ │ ├── Session.java
│ │ │ │ ├── Message.java
│ │ │ │ └── SessionStore.java
│ │ │ ├── channel/
│ │ │ │ ├── Channel.java
│ │ │ │ └── console/
│ │ │ │ └── ConsoleChannel.java
│ │ │ └── cli/
│ │ │ ├── OpenClawCli.java
│ │ │ ├── GatewayCommand.java
│ │ │ └── SendCommand.java
│ │ └── resources/
│ │ └── logback.xml
│ └── test/
│ └── java/ai/openclaw/
│ ├── config/
│ │ └── ConfigLoaderTest.java
│ ├── gateway/
│ │ ├── RpcProtocolTest.java
│ │ └── GatewayServerTest.java
│ ├── agent/
│ │ └── AgentExecutorTest.java
│ └── session/
│ └── SessionStoreTest.java
└── README.md
Total: ~20 production files, ~5 test files — project at ~/github/openclaw-java/
| Original TS File/Module | Java Equivalent | Notes |
|---|---|---|
src/index.ts |
Main.java |
Entry point |
src/config/zod-schema.ts |
OpenClawConfig.java + sub-configs |
POJOs replace Zod |
src/config/io.ts |
ConfigLoader.java |
No hot-reload in MVP |
src/gateway/server.ts |
GatewayServer.java |
Java-WebSocket |
src/gateway/protocol.ts |
RpcProtocol.java |
JSON-RPC envelope |
src/gateway/router.ts |
RpcRouter.java |
Method dispatch |
src/agents/agent-pi.ts |
AgentExecutor.java |
No tools, no sandbox |
src/agents/system-prompt.ts |
SystemPromptBuilder.java |
Reads IDENTITY.md |
| pi-ai SDK (Anthropic) | LlmProvider.java + AnthropicProvider.java |
Extensible interface |
src/config/sessions/store.ts |
SessionStore.java |
JSONL persistence |
src/channels/ (all) |
ConsoleChannel.java |
Stdin/stdout for MVP |
src/cli/program.ts |
OpenClawCli.java |
picocli |
src/cli/agent-cli.ts |
SendCommand.java |
WebSocket client |
Run all tests:
cd openclaw-java && ./gradlew test| Test File | What It Verifies |
|---|---|
ConfigLoaderTest.java |
Loads valid JSON config, rejects missing required fields, handles defaults |
RpcProtocolTest.java |
Serializes/deserializes RPC messages correctly |
GatewayServerTest.java |
Server starts on configured port, accepts WebSocket connections, responds to gateway.health |
AgentExecutorTest.java |
Constructs correct message array, mocks LLM client, returns response |
SessionStoreTest.java |
Creates sessions, appends messages, persists to JSONL, reloads from file |
Tip
These steps assume you have an OpenAI API key and a Telegram bot token.
-
Build the project:
cd openclaw-java && ./gradlew build
✅ Should compile without errors.
-
Create config file at
~/.openclaw-java/config.json:{ "gateway": { "port": 18789, "authToken": "test-token" }, "agent": { "provider": "anthropic", "apiKey": "sk-ant-...", "model": "claude-sonnet-4-20250514" } } -
Start the gateway:
java -jar build/libs/openclaw-java.jar gateway
✅ Should print "Gateway listening on ws://127.0.0.1:18789" and open the console channel prompt.
-
Chat in the console: Type a message and press Enter. ✅ Should print an AI-generated response from Claude.
-
Send via CLI (in another terminal):
java -jar build/libs/openclaw-java.jar send --message "Hello, who are you?"✅ Should connect to Gateway via WebSocket and print the response.
- ✅ LLM Provider: Anthropic as default, with extensible
LlmProviderinterface for future providers - ✅ Project location:
~/github/openclaw-java/ - ✅ Java version: JDK 21+ (virtual threads)
- ✅ Channel: Console (stdin/stdout) for MVP — no external messaging deps needed