A high-performance, type-safe, asynchronous Java 21+ library wrapping the Interactive Brokers (IBKR) Trader Workstation (TWS) API.
Specifically designed for algorithmic trading platforms, lightweight microservices, and Model Context Protocol (MCP) servers (enabling LLMs and AI agents like Claude Desktop to execute trade actions and query portfolios with ultra-low latency).
The official IBKR Java SDK relies on a legacy callback-based paradigm (EWrapper / EClientSocket) that is cumbersome to integrate with modern asynchronous web frameworks, reactive systems, and AI agent frameworks.
This wrapper modernizes the developer experience by introducing:
- Java 21 Native Records & Sealed Types: No Lombok or boilerplate. The entirety of IBKR's callbacks map to strongly-typed records.
- Asynchronous-First API: Returns
CompletableFutures for actions, handles timeouts gracefully, and features compile-time safety. - Built for AI/MCP Integration: Ideal for wrapping brokerage endpoints as tools for AI models.
- Zero-Overhead & GraalVM Ready: Fully compatible with ahead-of-time (AOT) compilation, launching in under 50ms with ~20MB RAM.
The wrapper separates concerns into three distinct layers to provide maximum architectural flexibility:
graph TD
Client[Client App / MCP Server Host] -->|CompletableFutures| Bridge[TwsSyncBridge]
Client -->|Event Listeners| Api[TwsApi Engine]
Bridge -->|Calls req* / place*| Api
Api -->|Dispatches Events| TwsEvent[TwsEvent Records]
Api <-->|Raw Socket API| Gateway[IB Gateway / TWS Workstation]
Gateway <-->|Internet| IBKR[Interactive Brokers Servers]
TwsApi(The Engine): Manages connection handshakes, reads signals, parses raw socket data via the background thread, and implements the low-levelEWrapper.TwsEvent(The Domain): A sealed interface containing records mapping to everyEWrappercallback (e.g.TickPrice,Position,OrderStatus, etc.).TwsSyncBridge(Stateful View): Aggregates incoming events into concurrent local caches, providing a higher-level stateful API (e.g.getPositions()) and bridging asynchronous callbacks into clean request-responseCompletableFutures.
Add the following dependency to your pom.xml (available on Maven Central):
<dependency>
<groupId>dev.prokop.ibkr</groupId>
<artifactId>tws-api</artifactId>
<version>0.0.4</version>
</dependency>To move from local development to production, the library supports multiple deployment patterns:
Compile your code directly into a single, self-contained binary containing all dependencies, static configurations, and no JVM requirement.
Prerequisites:
- GraalVM JDK 21+ (e.g.,
brew install --cask graalvm-jdk@21on macOS). - Install
native-imagetool.
Build Command:
mvn clean package -PnativeThe compiled native executable will be generated at target/tws-api-demo.
To build a highly optimized Docker image using GraalVM Native Image compilation, use this multi-stage Dockerfile:
# Stage 1: Build native image
FROM ghcr.io/graalvm/native-image-community:21 AS builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -Pnative
# Stage 2: Tiny runner image
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=builder /app/target/tws-api-demo /app/tws-api-demo
EXPOSE 4001
ENTRYPOINT ["/app/tws-api-demo"]This produces an image that is only ~30MB in size, contains no Java runtime, and boots in milliseconds.
The connection lifecycle uses CompletableFuture to coordinate and prevent requests from being fired before the API has established its initial handshake.
TwsApi twsApi = new TwsApi();
// Listen to specific account events
twsApi.on(TwsEvent.AccountSummary.class, event -> {
System.out.printf("Account: %s | %s = %s (%s)\n",
event.account(), event.tag(), event.value(), event.currency());
});
// Connect to IB Gateway (default ports: 4001 for Paper, 7496 for Live)
twsApi.connect("127.0.0.1", 4001, 1).thenRun(() -> {
System.out.println("Ready to transact!");
// Automatically queued and safely executed once handshake is complete
twsApi.reqAccountSummary("All", "NetLiquidation,TotalCashValue");
});The TwsSyncBridge maps individual event callbacks into unified collections and futures.
TwsApi twsApi = new TwsApi();
twsApi.connect("127.0.0.1", 4001, 1);
TwsSyncBridge bridge = new TwsSyncBridge(twsApi);
// Wait for initial portfolio sync to complete
bridge.ready().thenRun(() -> {
bridge.getPositions().thenAccept(positions -> {
System.out.printf("Synchronized %d positions:\n", positions.size());
positions.forEach(pos -> {
System.out.printf("- %s: %s units of %s @ cost basis %f\n",
pos.account(), pos.pos(), pos.contract().symbol(), pos.avgCost());
});
});
});Executing trades is highly structured and uses IBKR domain objects.
// Create Contract representation
Contract contract = new Contract();
contract.symbol("AAPL");
contract.secType("STK");
contract.exchange("SMART");
contract.currency("USD");
// Create Order specification
Order order = new Order();
order.action("BUY");
order.orderType("LMT");
order.totalQuantity(Decimal.fromDouble(10));
order.lmtPrice(185.50);
// Listen to order updates
twsApi.on(TwsEvent.OrderStatus.class, status -> {
System.out.printf("Order ID: %d | Status: %s | Filled: %s\n",
status.orderId(), status.status(), status.filled());
});
// Place order and get internal order tracking ID
int orderId = twsApi.placeOrder(contract, order);
System.out.printf("Submitted order. Allocated Order ID: %d\n", orderId);| Metric | Standard JVM HotSpot | GraalVM AOT Native |
|---|---|---|
| Startup Time | ~1.8 seconds | < 35 milliseconds |
| RAM Footprint | ~180 MB | ~22 MB |
| Connection Handshake | Synchronous Block | Non-blocking Future |
| CPU Overhead | Medium (JIT compilation) | Minimal (Precompiled) |
To build the project locally, compile the classes, and run all unit tests:
mvn clean testIf you want to map a new IBKR event or command:
- Verify the signature in the read-only vendored source under
src/main/java/com/ib/client/EWrapper.java. - Define a new record in
src/main/java/dev/prokop/ibkr/twsapi/TwsEvent.javamatching fields and types. - Implement/override the callback method in
TwsApi.java's internalEWrapperdelegate to dispatch your new record. - Add a delegate method in
TwsApi.javafor corresponding outbound requests if needed.
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.