Skip to main content
The Java SDK is currently being migrated to support the S2 v1 API. It is not yet recommended for production use with the current S2 platform.
The Java SDK will provide a comprehensive Java interface for interacting with S2, with support for modern Java features and reactive patterns.

Current Status

The Java SDK is undergoing migration to support the S2 v1 API. Once complete, it will provide:
  • Full v1 API compatibility
  • Support for Java 11+
  • CompletableFuture-based async APIs
  • Optional reactive streams support
  • Comprehensive type safety

Repository

The SDK is available at:

Java SDK Repository

View source code and track migration progress

Planned Features

Once migration is complete, the Java SDK will support:

Installation (Planned)

Maven

<dependency>
    <groupId>dev.s2</groupId>
    <artifactId>s2-sdk</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle

implementation 'dev.s2:s2-sdk:1.0.0'

Basic Usage (Planned)

import dev.s2.sdk.S2;
import dev.s2.sdk.S2Config;
import dev.s2.sdk.Basin;
import dev.s2.sdk.Stream;

public class Example {
    public static void main(String[] args) {
        // Initialize client
        S2 s2 = new S2(S2Config.builder()
            .accessToken("your_access_token")
            .build());
        
        // List basins
        List<BasinInfo> basins = s2.listBasins().join();
        
        // Get a stream
        Stream stream = s2.basin("my-basin").stream("my-stream");
        
        // Append records
        Producer producer = stream.producer();
        AppendAck ack = producer.append(
            AppendRecord.of("Hello, S2!".getBytes())
        ).join();
        
        // Read records
        Reader reader = stream.reader();
        reader.read().forEach(batch -> {
            batch.records().forEach(record -> {
                System.out.println("Record: " + new String(record.body()));
            });
        });
    }
}

Async Operations (Planned)

import java.util.concurrent.CompletableFuture;

// All operations return CompletableFuture
CompletableFuture<Basin> basinFuture = s2.createBasin(
    CreateBasinInput.builder()
        .name("my-basin")
        .build()
);

basinFuture.thenAccept(basin -> {
    System.out.println("Basin created: " + basin.name());
}).exceptionally(error -> {
    System.err.println("Error: " + error.getMessage());
    return null;
});

Working with Basins (Planned)

import dev.s2.sdk.model.*;

// Create a basin
Basin basin = s2.createBasin(
    CreateBasinInput.builder()
        .name("my-basin")
        .config(BasinConfig.builder()
            .defaultStreamConfig(StreamConfig.builder()
                .retentionPolicy(RetentionPolicy.age(7 * 24 * 60 * 60))
                .build())
            .build())
        .build()
).join();

// List basins with pagination
List<BasinInfo> allBasins = new ArrayList<>();
s2.listAllBasins().forEach(allBasins::add);

// Delete a basin
s2.deleteBasin(DeleteBasinInput.of("my-basin")).join();

Writing Records (Planned)

// Get stream
Stream stream = s2.basin("my-basin").stream("my-stream");
Producer producer = stream.producer();

// Append single record
AppendAck ack = producer.append(
    AppendRecord.builder()
        .body("my data".getBytes())
        .header("content-type", "text/plain")
        .build()
).join();

System.out.println("Sequence number: " + ack.seqNum());

// Batch append
List<AppendRecord> records = List.of(
    AppendRecord.of("record 1".getBytes()),
    AppendRecord.of("record 2".getBytes()),
    AppendRecord.of("record 3".getBytes())
);

AppendAck batchAck = stream.appendBatch(
    AppendInput.of(records)
).join();

Reading Records (Planned)

// Read with iteration
Stream stream = s2.basin("my-basin").stream("my-stream");
Reader reader = stream.reader();

reader.read().forEach(batch -> {
    batch.records().forEach(record -> {
        System.out.println("SeqNum: " + record.seqNum());
        System.out.println("Body: " + new String(record.body()));
    });
});

// Read from specific position
reader.read(ReadOptions.builder()
    .startSeqNum(100L)
    .limit(1000)
    .build()
).forEach(batch -> {
    // Process batch
});

// Single read
ReadBatch batch = stream.read(ReadInput.builder()
    .startSeqNum(0L)
    .limit(100)
    .build()
).join();

Reactive Streams (Planned)

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

// Using Project Reactor
Stream stream = s2.basin("my-basin").stream("my-stream");
ReactiveReader reactiveReader = stream.reactiveReader();

Flux<ReadBatch> batches = reactiveReader.read();
batches.subscribe(
    batch -> {
        // Process batch
    },
    error -> {
        System.err.println("Error: " + error);
    },
    () -> {
        System.out.println("Stream complete");
    }
);

Builder Pattern (Planned)

// Fluent configuration
S2 s2 = S2.builder()
    .accessToken("your_token")
    .baseUrl("https://custom.s2.dev")
    .timeout(Duration.ofSeconds(30))
    .build();

// Input builders
CreateStreamInput input = CreateStreamInput.builder()
    .name("my-stream")
    .config(StreamConfig.builder()
        .timestamping(TimestampingConfig.builder()
            .mode(TimestampingMode.CLIENT_REQUIRE)
            .build())
        .build())
    .build();

Error Handling (Planned)

import dev.s2.sdk.exception.*;

try {
    s2.createBasin(CreateBasinInput.of("my-basin")).join();
} catch (CompletionException e) {
    Throwable cause = e.getCause();
    if (cause instanceof UnauthorizedException) {
        System.err.println("Invalid access token");
    } else if (cause instanceof NotFoundException) {
        System.err.println("Resource not found");
    } else if (cause instanceof ConflictException) {
        System.err.println("Resource already exists");
    } else {
        System.err.println("Error: " + cause.getMessage());
    }
}

Alternative Options

While the Java SDK is being migrated, you can:
  1. Use the REST API directly with libraries like OkHttp or Java 11+ HttpClient:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://s2.dev/api/basins"))
    .header("Authorization", "Bearer " + accessToken)
    .build();

HttpResponse<String> response = client.send(
    request,
    HttpResponse.BodyHandlers.ofString()
);
  1. Use another SDK if you can integrate it into your stack:

Contributing

We welcome contributions to help complete the Java SDK migration! Check the repository for:
  • Open issues and migration tasks
  • Contribution guidelines
  • Development setup instructions

GitHub Repository

Contribute to the migration

REST API Reference

Use the API directly

Discord Community

Discuss SDK development

GitHub Issues

Report issues or request features

Stay Updated

To get notified when the Java SDK migration is complete:

Next Steps

While waiting for the Java SDK:

Build docs developers (and LLMs) love