Resources / Blog / Java Client SDK 1.0 Now Available
AnnouncementOctober 9, 20266 min read

Java Client SDK 1.0 Now Available

Today we're pleased to announce the release of the official Java Client SDK for EventSourcingDB, version 1.0. If you're building on the JVM, you can now add a single dependency to your project and write your first event from Java a few minutes later.

The SDK is written for modern Java, and it doesn't stop at the client: it brings Testcontainers support for integration tests and a Spring Boot starter that wires everything up for you. In this post, we'll walk you from the first event all the way to a Spring Boot test that runs against a real database.

Why Java Matters#

For decades, Java has carried the systems that run businesses: core banking, insurance claims, logistics, public administration. These systems tend to live for a long time, they change hands between teams, and sooner or later someone asks how a piece of data got into its current state.

That's the question Event Sourcing answers by design, because it stores every change as an event instead of overwriting the past. An official SDK brings EventSourcingDB straight into Java code, with the same feature set as on every other platform we support.

Built for Modern Java#

The SDK requires Java 21 or later, and it uses the language the way current Java code does. Here's what writing an event looks like. You create a client with the URL of your EventSourcingDB instance and its API token. Then you describe the event: a source that identifies the system it comes from (a URI that is never called), a subject that says what the event is about, written as a path like /books/42, a type that names what happened, and your data. Until the database has accepted it, the event is a candidate, hence the name EventCandidate:

record BookAcquired(String title, String author, String isbn) {}

var client = new Client(URI.create("http://localhost:3000"), "secret");

var candidate = new EventCandidate(
  "https://library.eventsourcingdb.io",
  "/books/42",
  "io.eventsourcingdb.library.book-acquired",
  new BookAcquired("2001 – A Space Odyssey", "Arthur C. Clarke", "978-0756906788")
);

client.writeEvents(List.of(candidate));

The events you write and the options you pass are records. Reading, observing, querying, and listing all return a lazy Stream that fetches its results as you consume them. Since such a stream holds a connection, you close it with try-with-resources, just like a file:

try (var events = client.readEvents("/books/42", new ReadEventsOptions(false))) {
  events.forEach(event -> {
    var bookAcquired = event.data(BookAcquired.class);
    // ...
  });
}

The false keeps the read to /books/42 itself; with true, you'd also get the events of the subjects below it, such as /books/42/loans. The client itself is AutoCloseable as well, so you close it when your application shuts down.

Errors are unchecked exceptions, so they don't force throws clauses through your codebase. Event data goes through Jackson 3, and you can hand the client your application's own JsonMapper, so your event data follows the same naming and the same modules as the rest of your JSON.

The API is annotated with JSpecify, so your IDE and tools like NullAway know where null is allowed and where it isn't. And for applications that use the Java module system, the SDK ships as an explicit module, io.thenativeweb.eventsourcingdb, that you require with one line.

What's Included#

The 1.0 release covers everything you need to build applications on EventSourcingDB. It meets every item on the checklist that all our official SDKs have to pass, our Compliance Criteria.

A precondition makes a write conditional: it only goes through if, for example, nothing has been written to the subject yet, or if the last event on the subject is still the one you read. That's how you guard against concurrent changes. The SDK supports all four preconditions, so a write can also require that a subject already has events, or that an EventQL query holds true. Every write can also carry OpenTelemetry trace context.

Reads can include nested subjects, run in chronological or anti-chronological order, and start or stop at a given event ID. They can also start at the latest event of a given type, which lets you begin at your latest snapshot instead of at the very first event.

Observing uses the same kind of stream, but it stays open and delivers new events as they're written. If the connection stalls, the SDK notices instead of waiting forever. Queries in EventQL, EventSourcingDB's query language for events, stream their rows back as well, either as JSON or deserialized straight into your own types.

On top of that, the SDK registers event schemas, which EventSourcingDB enforces for all events of a type, past and future. It lists subjects and event types, and it verifies the hashes and signatures of events, so you can check that what you read is exactly what was written.

From Dependency to First Event#

The SDK is published on Maven Central. With Gradle, add this to your build.gradle.kts:

implementation("io.thenativeweb:eventsourcingdb:1.0.0")

With Maven, add a dependency with the group ID io.thenativeweb, the artifact ID eventsourcingdb, and the version 1.0.0 to your pom.xml. If you don't have an instance running yet, our guide on running EventSourcingDB starts one with a single Docker command; the API token is the one you set there.

Then create a client as shown above, and call ping to check that the instance is reachable:

client.ping();

From there, the code from the previous section writes and reads your first event.

The Java SDK documentation covers everything else, from configuring the HTTP client to observing events.

Testing and Spring Boot#

Code that talks to the event store is best tested against a real database, not a mock, because a mock only imitates what you actually want to check: whether a precondition holds, and whether the events you read back are the ones you wrote. We made the full case in Testing Without Mocks.

The artifact io.thenativeweb:eventsourcingdb-testcontainers makes that straightforward. The SDK's Container class starts EventSourcingDB in a container, hands you a client for it, and removes the container when you're done:

try (var container = new Container()) {
  container.start();

  var client = container.getClient();
  // ...
}

If your application runs on Spring Boot 4, the starter io.thenativeweb:eventsourcingdb-spring-boot-starter goes one step further. Set the URL and the API token in your configuration, and the starter creates a client that you can inject wherever you need it:

eventsourcingdb.base-url=http://localhost:3000
eventsourcingdb.api-token=secret

The starter serializes event data with the JsonMapper of your application, and it closes the client when the application shuts down. In tests, the two artifacts work together. With Spring Boot's Testcontainers support (org.springframework.boot:spring-boot-testcontainers) on the test classpath, declare the container as a bean with @ServiceConnection, and Spring Boot starts it and connects the client to it, without any properties:

@TestConfiguration(proxyBeanMethods = false)
class ContainerConfiguration {
  @Bean
  @ServiceConnection
  Container eventSourcingDb() {
    return new Container();
  }
}

Import that configuration into a test, and the injected client talks to EventSourcingDB in a container:

@SpringBootTest
@Import(ContainerConfiguration.class)
class LibraryTest {
  @Autowired
  Client client;

  @Test
  void connectsToEventSourcingDb() {
    client.ping();
  }
}

EventSourcingDB Speaks Java#

One dependency gets you to your first event. The Testcontainers module and the Spring Boot starter add real integration tests against a real database, and a client that Spring Boot hands you as a bean.

With this release, EventSourcingDB offers official client SDKs for eight languages: .NET, Elixir, Go, Java, JavaScript/TypeScript, PHP, Python, and Rust. You can compare them on our Client SDKs overview page.

The SDK gives you direct access to the database. If you're looking for a complete framework for CQRS and Event Sourcing on the JVM, one that takes care of commands, state, and event processing for you, take a look at OpenCQRS by our friends at Digital Frontiers.

Add the dependency, write your first event, and tell us how it goes. Everything you need is in the Java SDK documentation. We'd love to hear your questions and feedback on GitHub or through our contact form.

Written by
Golo Roden

CTO and founder at the native web. www.thenativeweb.io