This directory contains comprehensive sample applications that showcase various usage patterns of the Iggy java client SDK, from basic operations to advanced multi-tenant scenarios.
Java 17 and Gradle 9.2.1 are recommended for running the examples.
The Java SDK speaks the VSR (Viewstamped Replication) wire protocol, so the examples run against the VSR server.
Iggy requires valid credentials to authenticate client requests. The examples assume that the server is using the default root credentials, set through environment variables before starting the server:
macOS/Linux:
export IGGY_ROOT_USERNAME=iggy export IGGY_ROOT_PASSWORD=iggy cargo run --bin iggy-server
Windows(Powershell):
$env:IGGY_ROOT_USERNAME = "iggy" $env:IGGY_ROOT_PASSWORD = "iggy"
Note
This setup is intended only for development and testing, not production use.
By default, all server data is stored in the local_data directory (this can be changed via system.path in config.toml).
Root credentials are applied only on the very first startup, when no data directory exists yet. Once the server has created and populated the data directory, the existing stored credentials will always be used, and setting the environment variables will no longer override them.
If the server has already been started once and your example returns Error: InvalidCredentials, then this means the stored credentials differ from the defaults. Delete the existing data directory, then start the server again with the environment variables set.
You can also customize the server using environment variables:
## Example: set a custom TCP address IGGY_TCP_ADDRESS=0.0.0.0:8090 cargo run --bin iggy-server
A good introduction for newcomers to Iggy:
./gradlew runGettingStartedProducer ./gradlew runGettingStartedConsumer
Shows metadata management using custom headers:
./gradlew runMessageHeadersProducer ./gradlew runMessageHeadersConsumer
Demonstrates using header keys and values for message metadata instead of payload-based typing, with header-based message routing.
JSON envelope pattern for polymorphic message handling:
./gradlew runMessageEnvelopeProducer ./gradlew runMessageEnvelopeConsumer
Uses MessagesGenerator to create OrderCreated, OrderConfirmed, and OrderRejected messages wrapped in JSON envelopes for type identification.
Complex example demonstrating enterprise-level isolation:
./gradlew runMultiTenantProducer ./gradlew runMultiTenantConsumer
Features multiple tenant setup, user creation with stream-specific permissions, concurrent producers/consumers across tenants, and security isolation.
Testing and benchmarking support:
./gradlew runSinkDataProducer
Produces high-throughput data (1000+ messages per batch) with realistic user records.
Building streams with advanced configuration:
./gradlew runStreamBasic
Shows how to use the stream builder API to create and configure streams with custom settings.
Non-blocking batch production with concurrent request submission:
./gradlew runAsyncProducer
Shows:
Non-blocking async consumption with advanced patterns:
./gradlew runAsyncConsumer
Shows:
CRITICAL ASYNC PATTERN - Thread Pool Management:
The async client uses Netty's event loop threads for I/O operations. NEVER block these threads with:
.join() or .get() inside thenApply/thenAcceptThread.sleep()If your message processing involves blocking operations, offload to a separate thread pool using thenApplyAsync(fn, executor).
Demonstrates secure TLS-encrypted TCP connections:
./gradlew runTcpTlsProducer ./gradlew runTcpTlsConsumer
These examples require a TLS-enabled Iggy server. Start the server with:
IGGY_TCP_TLS_ENABLED=true \ IGGY_TCP_TLS_CERT_FILE=core/certs/iggy_cert.pem \ IGGY_TCP_TLS_KEY_FILE=core/certs/iggy_key.pem \ cargo run --bin iggy-server
Uses IggyTcpClientBuilder with TLS options (enableTls, tlsDomain, tlsCaCertPath) to establish TLS-encrypted TCP connections with CA certificate verification.
The Iggy Java SDK provides two client types: blocking (synchronous) and async (non-blocking). Choose based on your use case:
CompletableFutureclient.connect() .thenCompose(v -> client.login()) .thenCompose(identity -> client.streams().createStream("my-stream")) .thenAccept(stream -> System.out.println("Created: " + stream.name())) .exceptionally(ex -> { System.err.println("Error: " + ex.getMessage()); return null; });
List<CompletableFuture<SendMessagesResponse>> sends = new ArrayList<>(); for (int i = 0; i < 10; i++) { sends.add(client.messages().sendMessages(...)); } CompletableFuture.allOf(sends.toArray(new CompletableFuture[0])).join();
The client accepts these calls without blocking, but its single VSR-pinned TCP connection processes them in order. Batch more messages into each send to improve throughput.
// WRONG - blocks Netty event loop client.messages().pollMessages(...) .thenAccept(polled -> { saveToDatabase(polled); // blocking I/O! }); // CORRECT - offloads to processing pool var processingPool = Executors.newFixedThreadPool(8); client.messages().pollMessages(...) .thenAcceptAsync(polled -> { saveToDatabase(polled); // runs on processingPool }, processingPool);