| name | ccxt-java |
| description | CCXT cryptocurrency exchange library for Java developers. Covers both REST API (standard) and WebSocket API (real-time). Helps install CCXT, connect to exchanges, fetch market data, place orders, stream live tickers/orderbooks, handle authentication, and manage errors in Java projects. Use when working with crypto exchanges in Java applications, trading systems, or financial software. Requires Java 21+. |
CCXT for Java
A comprehensive guide to using CCXT in Java projects for cryptocurrency exchange integration.
Installation
Via Gradle
// build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.ccxt:ccxt:latest.release'
}
Via Maven
<dependency>
<groupId>io.github.ccxt</groupId>
<artifactId>ccxt</artifactId>
<version>LATEST</version>
</dependency>
Requirements
- Java 21 or higher (uses virtual threads)
Quick Start
REST API
import io.github.ccxt.exchanges.Binance;
import io.github.ccxt.types.Ticker;
Binance exchange = new Binance();
exchange.loadMarkets(false);
Ticker ticker = exchange.fetchTicker("BTC/USDT");
System.out.println(ticker.last);
WebSocket API - Real-time Updates
import io.github.ccxt.exchanges.pro.Binance;
var exchange = new Binance();
exchange.loadMarkets(false);
while (true) {
Ticker ticker = exchange.watchTicker("BTC/USDT");
System.out.println(ticker.last);
}
Architecture: Typed Subclasses
Each exchange has two classes following the Go pattern:
BinanceCore - transpiled untyped class (internal, extends BinanceApi extends Exchange)
Binance - typed wrapper extending Core with typed overloads (user-facing)
Binance exchange = new Binance();
Ticker ticker = exchange.fetchTicker("BTC/USDT");
List<Trade> trades = exchange.fetchTrades("BTC/USDT");
Object raw = exchange.publicGetTicker24hr(params).join();
exchange.apiKey = "...";
exchange.secret = "...";
The typed methods use Java method overloading. They coexist safely with untyped methods because Java resolves overloads at compile time: BinanceCore.java is compiled without knowledge of Binance.java's typed overloads, so internal calls always bind to untyped varargs.
REST vs WebSocket
| Feature | REST API | WebSocket API |
|---|
| Use for | One-time queries, placing orders | Real-time monitoring, live price feeds |
| Import | io.github.ccxt.exchanges.Binance | io.github.ccxt.exchanges.pro.Binance |
| Methods | fetch* (fetchTicker, fetchOrderBook) | watch* (watchTicker, watchOrderBook) |
| Returns | Typed objects (Ticker, List<Trade>) | CompletableFuture<Object> (call .join()) |
| Speed | Slower (HTTP request/response) | Faster (persistent connection) |
| Rate limits | Strict (1-2 req/sec) | More lenient (continuous stream) |
| Best for | Trading, account management | Price monitoring, arbitrage detection |
Creating Exchange Instance
REST API
import io.github.ccxt.exchanges.Binance;
import java.util.Map;
import java.util.HashMap;
Binance exchange = new Binance();
Map<String, Object> config = new HashMap<>();
config.put("apiKey", "YOUR_API_KEY");
config.put("secret", "YOUR_SECRET");
Binance exchange = new Binance(config);
WebSocket API
import io.github.ccxt.exchanges.pro.Binance;
var exchange = new Binance();
Map<String, Object> config = new HashMap<>();
config.put("apiKey", "YOUR_API_KEY");
config.put("secret", "YOUR_SECRET");
var exchange = new Binance(config);
Dynamic Instantiation (generic, untyped)
import io.github.ccxt.Exchange;
Exchange exchange = Exchange.dynamicallyCreateInstance("binance", config);
Object ticker = exchange.fetchTicker("BTC/USDT").join();
Common REST Operations
Loading Markets
Map<String, MarketInterface> markets = exchange.loadMarkets(false);
MarketInterface btcMarket = markets.get("BTC/USDT");
System.out.println(btcMarket.base);
System.out.println(btcMarket.quote);
System.out.println(btcMarket.active);
Fetching Ticker
Ticker ticker = exchange.fetchTicker("BTC/USDT");
System.out.println(ticker.last);
System.out.println(ticker.bid);
System.out.println(ticker.ask);
System.out.println(ticker.baseVolume);
CompletableFuture<Ticker> future = exchange.fetchTickerAsync("BTC/USDT", null);
Fetching Order Book
OrderBook orderbook = exchange.fetchOrderBook("BTC/USDT", null, null);
System.out.println(orderbook.bids.get(0));
System.out.println(orderbook.asks.get(0));
OrderBook orderbook = exchange.fetchOrderBook("BTC/USDT", 5L, null);
Fetching Trades
List<Trade> trades = exchange.fetchTrades("BTC/USDT");
for (Trade t : trades) {
System.out.println(t.datetime + " " + t.side + " " + t.price + " x " + t.amount);
}
List<Trade> trades = exchange.fetchTrades("BTC/USDT", null, 20L, null);
Fetching OHLCV (Candlesticks)
List<OHLCV> candles = exchange.fetchOHLCV("BTC/USDT", "1h", null, 10L, null);
for (OHLCV c : candles) {
System.out.println(c.timestamp + " O:" + c.open + " H:" + c.high + " L:" + c.low + " C:" + c.close);
}
Creating Orders
Limit Order
Order order = exchange.createOrder("BTC/USDT", "limit", "buy", 0.01, 50000.0, null);
System.out.println(order.id);
Order order = exchange.createOrder("BTC/USDT", "limit", "sell", 0.01, 60000.0, null);
Market Order
Order order = exchange.createOrder("BTC/USDT", "market", "buy", 0.01, null, null);
Order order = exchange.createOrder("BTC/USDT", "market", "sell", 0.01, null, null);
Fetching Balance
Balances balance = exchange.fetchBalance((Map<String, Object>) null);
System.out.println(balance);
Fetching Orders
List<Order> openOrders = exchange.fetchOpenOrders("BTC/USDT");
List<Order> closedOrders = exchange.fetchClosedOrders("BTC/USDT");
Order order = exchange.fetchOrder("orderId123", "BTC/USDT", null);
Canceling Orders
Order cancelled = exchange.cancelOrder("orderId123", "BTC/USDT", null);
List<Order> cancelled = exchange.cancelAllOrders("BTC/USDT", null);
Exchange-Specific (Implicit) API
Each exchange class inherits exchange-specific endpoint methods from its Api class. These return CompletableFuture<Object> (untyped):
import io.github.ccxt.exchanges.Binance;
Binance exchange = new Binance();
exchange.loadMarkets(false);
Map<String, Object> params = new HashMap<>();
params.put("symbol", "BTCUSDT");
Object rawTicker = exchange.publicGetTicker24hr(params).join();
Object accountInfo = exchange.sapiGetAccountInfo(null).join();
Object exchangeInfo = exchange.publicGetExchangeInfo(null).join();
WebSocket Operations (Real-time)
WebSocket classes are in io.github.ccxt.exchanges.pro. They return CompletableFuture<Object>:
Watching Ticker
import io.github.ccxt.exchanges.pro.Binance;
var exchange = new Binance();
exchange.loadMarkets(false);
while (true) {
Ticker ticker = exchange.watchTicker("BTC/USDT");
System.out.println("Last: " + ticker.last);
}
Watching Order Book
var exchange = new Binance();
exchange.loadMarkets(false);
while (true) {
OrderBook ob = exchange.watchOrderBook("BTC/USDT");
System.out.println("Best bid: " + ob.bids.get(0));
}
Watching Trades
var exchange = new Binance();
exchange.loadMarkets(false);
while (true) {
List<Trade> trades = exchange.watchTrades("BTC/USDT");
for (Trade t : trades) {
System.out.println(t.price + " " + t.amount + " " + t.side);
}
}
Watching Your Orders (Private)
Map<String, Object> config = Map.of("apiKey", "KEY", "secret", "SECRET");
var exchange = new Binance(config);
exchange.loadMarkets(false);
while (true) {
List<Order> orders = exchange.watchOrders("BTC/USDT");
System.out.println(orders);
}
Sync vs Async
Java CCXT provides three patterns — and the symmetry applies to both REST fetch* and WS watch* methods.
1. Typed Sync (blocking)
Binance exchange = new Binance();
Ticker ticker = exchange.fetchTicker("BTC/USDT");
var wsExchange = new io.github.ccxt.exchanges.pro.Binance();
Ticker tick = wsExchange.watchTicker("BTC/USDT");
2. Typed Async (non-blocking)
CompletableFuture<Ticker> future = exchange.fetchTickerAsync("BTC/USDT", null);
future.thenAccept(ticker -> System.out.println(ticker.last));
CompletableFuture<Ticker> wsFuture = wsExchange.watchTickerAsync("BTC/USDT", null);
wsFuture.thenAccept(tick -> System.out.println(tick.last));
CompletableFuture.allOf(
wsExchange.watchTickerAsync("BTC/USDT", null),
wsExchange.watchOrderBookAsync("ETH/USDT", null, null)
).join();
Every typed fetch* and watch* method has a matching *Async overload at every supported arity, including zero-arg (where the method allows it). Same return-type symmetry: Ticker fetchTicker(...) ↔ CompletableFuture<Ticker> fetchTickerAsync(...); Tickers watchTickers(...) ↔ CompletableFuture<Tickers> watchTickersAsync(...).
3. Untyped (CompletableFuture<Object>)
Exchange exchange = Exchange.dynamicallyCreateInstance("binance", null);
Object result = exchange.fetchTicker("BTC/USDT").join();
Complete Method Reference
Market Data Methods
Tickers & Prices
fetchTicker(symbol) - Fetch ticker for one symbol
fetchTickers(symbols, params) - Fetch multiple tickers
fetchBidsAsks(symbols, params) - Fetch best bid/ask
fetchLastPrices(symbols, params) - Fetch last prices
fetchMarkPrice(symbol, params) - Fetch mark price (derivatives)
Order Books
fetchOrderBook(symbol, limit, params) - Fetch order book
Trades
fetchTrades(symbol, since, limit, params) - Fetch public trades
fetchMyTrades(symbol, since, limit, params) - Fetch your trades (auth required)
OHLCV (Candlesticks)
fetchOHLCV(symbol, timeframe, since, limit, params) - Fetch candlestick data
Account & Balance
fetchBalance(params) - Fetch account balance (auth required)
fetchAccounts(params) - Fetch sub-accounts
fetchLedger(code, since, limit, params) - Fetch ledger history
Trading Methods
Creating Orders
createOrder(symbol, type, side, amount, price, params) - Create order
createOrders(orders, params) - Create multiple orders
editOrder(id, symbol, type, side, amount, price, params) - Modify order
Managing Orders
fetchOrder(id, symbol, params) - Fetch single order
fetchOrders(symbol, since, limit, params) - Fetch all orders
fetchOpenOrders(symbol, since, limit, params) - Fetch open orders
fetchClosedOrders(symbol, since, limit, params) - Fetch closed orders
cancelOrder(id, symbol, params) - Cancel single order
cancelAllOrders(symbol, params) - Cancel all orders
Derivatives & Futures
fetchPosition(symbol, params) - Fetch single position
fetchPositions(symbols, params) - Fetch all positions
fetchFundingRate(symbol, params) - Current funding rate
fetchFundingRateHistory(symbol, since, limit, params) - Funding rate history
setLeverage(leverage, symbol, params) - Set leverage
setMarginMode(marginMode, symbol, params) - Set margin mode
Deposits & Withdrawals
fetchDepositAddress(code, params) - Get deposit address
withdraw(code, amount, address, tag, params) - Withdraw funds
transfer(code, amount, fromAccount, toAccount, params) - Internal transfer
fetchDeposits(code, since, limit, params) - Fetch deposit history
fetchWithdrawals(code, since, limit, params) - Fetch withdrawal history
Fees
fetchTradingFee(symbol, params) - Trading fee for symbol
fetchTradingFees(params) - All trading fees
WebSocket Methods
All REST methods have WebSocket equivalents with watch* prefix:
watchTicker(symbol) - Watch single ticker
watchTickers(symbols) - Watch multiple tickers
watchOrderBook(symbol) - Watch order book updates
watchTrades(symbol) - Watch public trades
watchOHLCV(symbol, timeframe) - Watch candlestick updates
watchBalance() - Watch balance updates (auth required)
watchOrders(symbol) - Watch your order updates (auth required)
watchMyTrades(symbol) - Watch your trade updates (auth required)
Optional Parameters
Java typed methods use null for optional parameters:
List<Trade> trades = exchange.fetchTrades("BTC/USDT", sinceTimestamp, 100L, extraParams);
List<Trade> trades = exchange.fetchTrades("BTC/USDT", null, 100L, null);
List<Trade> trades = exchange.fetchTrades("BTC/USDT");
Authentication
Setting API Keys
Map<String, Object> config = new HashMap<>();
config.put("apiKey", System.getenv("BINANCE_API_KEY"));
config.put("secret", System.getenv("BINANCE_SECRET"));
Binance exchange = new Binance(config);
exchange.apiKey = System.getenv("BINANCE_API_KEY");
exchange.secret = System.getenv("BINANCE_SECRET");
Testing Authentication
try {
Balances balance = exchange.fetchBalance((Map<String, Object>) null);
System.out.println("Authentication successful!");
} catch (CompletionException e) {
if (e.getCause() instanceof AuthenticationError) {
System.out.println("Invalid API credentials");
}
}
Error Handling
Exception Hierarchy
BaseError
+- NetworkError (recoverable - retry)
| +- RequestTimeout
| +- ExchangeNotAvailable
| +- RateLimitExceeded
| +- DDoSProtection
+- ExchangeError (non-recoverable - don't retry)
+- AuthenticationError
+- InsufficientFunds
+- InvalidOrder
+- BadSymbol
+- NotSupported
Basic Error Handling
Typed sync methods (fetchTicker, createOrder, fetchBalance, etc.) unwrap
CompletionException internally and rethrow the underlying typed ccxt error,
so users write idiomatic Java try/catch with multiple typed catch blocks
in most-specific → least-specific order — same shape as catching
ArrayIndexOutOfBoundsException / IOException from the JDK:
import io.github.ccxt.errors.*;
import io.github.ccxt.exchanges.Binance;
import io.github.ccxt.types.Ticker;
Binance exchange = new Binance();
try {
Ticker ticker = exchange.fetchTicker("BTC/USDT");
} catch (NetworkError e) {
System.out.println("Network error - retry: " + e.getMessage());
} catch (ExchangeError e) {
System.out.println("Exchange error - do not retry: " + e.getMessage());
}
No CompletionException boilerplate, no .getCause() unwrap needed.
Java pitfalls when catching ccxt errors
-
You cannot multi-catch a parent and child error together. Java forbids it:
catch (NetworkError | BaseError e) { ... }
catch (NetworkError e) { ... }
catch (BaseError e) { ... }
-
Passing null to a sync method can be ambiguous for the 13 zero-required-param methods
(fetchBalance, fetchOrders, fetchMyTrades, fetchOpenOrders, fetchClosedOrders,
fetchCanceledOrders, fetchTime, fetchStatus, fetchTickers, fetchPositions,
fetchAccounts, fetchCurrencies, fetchMarkets) plus their *Ws siblings. These ship both
a typed fetchX(Map<String, Object> params) and the base fetchX(Object...) varargs, so a
bare null matches both:
exchange.fetchBalance(null);
exchange.fetchBalance();
exchange.fetchBalance((Map<String, Object>) null);
Same applies to fetchBalanceAsync(null) etc. For methods outside the list, the typed
zero-arg form doesn't exist — pass an explicit argument or use the cast form.
-
The JVM stays alive after main() returns because of internal HTTP/scheduler threads
(Netty event loop, virtual-thread executors per WS connection). Call exchange.close() to
release them; as a last resort System.exit(0) will force-exit. Avoid Runtime.addShutdownHook
— a shutdown hook runs during JVM shutdown, it doesn't trigger one.
Specific Exception Handling
Multi-catch and ordering work exactly as JDK conventions expect:
try {
Order order = exchange.createOrder("BTC/USDT", "limit", "buy", 0.01, 50000.0, null);
} catch (InsufficientFunds e) {
System.out.println("Not enough balance: " + e.getMessage());
} catch (InvalidOrder e) {
System.out.println("Invalid order params: " + e.getMessage());
} catch (AuthenticationError e) {
System.out.println("Check your API credentials: " + e.getMessage());
} catch (RateLimitExceeded | DDoSProtection e) {
Thread.sleep(30_000);
} catch (NetworkError e) {
Thread.sleep(2_000);
} catch (ExchangeError e) {
System.out.println("Exchange refused: " + e.getMessage());
} catch (BaseError e) {
System.out.println("CCXT error: " + e.getMessage());
}
Note: each catch clause must be for a single class; you cannot multi-catch
NetworkError | BaseError because BaseError is a parent of NetworkError.
The order above (most-specific to least-specific) is the JDK convention.
Async Error Handling
For async methods (fetchTickerAsync, createOrderAsync, …), the returned
CompletableFuture wraps exceptions in CompletionException. Use the
Helpers.unwrap() helper inside .exceptionally(...) to peel the wrapper
and pattern-match the underlying ccxt error:
import io.github.ccxt.Helpers;
exchange.fetchTickerAsync("BTC/USDT")
.thenAccept(t -> System.out.println(t.last()))
.exceptionally(throwable -> {
Throwable cause = Helpers.unwrap(throwable);
return switch (cause) {
case RateLimitExceeded e -> { backoff(); yield null; }
case NetworkError e -> { retry(); yield null; }
case AuthenticationError e -> { refreshCredentials(); yield null; }
case ExchangeError e -> { logExchangeError(e); yield null; }
case BaseError e -> { logCcxtError(e); yield null; }
default -> throw new java.util.concurrent.CompletionException(cause);
};
});
If you'd rather block and use the sync exception style, you can also do
Helpers.joinUnwrapped(future) instead of calling .join() directly — that's
the same helper the typed sync wrappers use internally.
Rate Limiting
Built-in Rate Limiter (Enabled by Default)
Binance exchange = new Binance();
System.out.println(exchange.enableRateLimit);
System.out.println(exchange.rateLimit);
Proxy Configuration
exchange.httpProxy = "http://your-proxy-host:port";
exchange.httpsProxy = "https://your-proxy-host:port";
exchange.socksProxy = "socks://your-proxy-host:port";
Common Pitfalls
Typed vs Untyped Methods
Binance exchange = new Binance();
Ticker ticker = exchange.fetchTicker("BTC/USDT");
Exchange exchange = Exchange.dynamicallyCreateInstance("binance", null);
Object result = exchange.fetchTicker("BTC/USDT").join();
Null for Optional Parameters
exchange.fetchBalance(null);
exchange.fetchBalance((Map<String, Object>) null);
Wrong Symbol Format
"BTCUSDT"
"BTC-USDT"
"btc/usdt"
"BTC/USDT"
REST for Real-time Monitoring
while (true) {
Ticker ticker = exchange.fetchTicker("BTC/USDT");
Thread.sleep(1000);
}
var wsExchange = new io.github.ccxt.exchanges.pro.Binance();
wsExchange.loadMarkets(false);
while (true) {
Ticker ticker = wsExchange.watchTicker("BTC/USDT");
}
Troubleshooting
Common Issues
1. "Java 21 required"
- Solution: CCXT Java requires Java 21+ for virtual threads
2. "RateLimitExceeded"
- Solution: Rate limiting is enabled by default. If you disabled it, re-enable with
exchange.enableRateLimit = true
3. "AuthenticationError"
- Solution: Check API key and secret
- Verify API key permissions on exchange
- Check system clock is synced
4. "NotSupported"
- Solution: Check
exchange.has map for method availability before calling
5. Typed methods not visible
- Solution: Use the concrete exchange type (
Binance exchange = new Binance()) not the generic Exchange type
Debugging
exchange.verbose = true;
System.out.println(exchange.has);
Learn More