Send data to Deephaven from a Java client
This guide shows how to send data to Deephaven from an external Java application using the Deephaven Java client and input tables. An input table is a Deephaven table that a client can write to. Every type of input table accepts new rows, and keyed input tables also let the client update and delete rows.
Note
If your data source can run directly on the Deephaven server, write the data from a server-side script instead. You can create and use input tables there, or use a TablePublisher or DynamicTableWriter. Server-side ingestion is generally more efficient because it avoids network overhead.
When to write to input tables from a Java client
Common reasons to write to an input table from a Java client include:
- Managing reference data: Upload configuration, lookup tables, or static datasets that may need updates.
- Tracking state: Maintain the latest status per entity (e.g., device status, order state, user preferences).
- Interactive editing: Allow users or external systems to add, update, or remove records.
- Forwarding external data: Relay data from message queues, APIs, or sensors running in separate JVMs.
Setup
Add the Deephaven Java client dependencies to your project. Replace <version> with the version that matches your Deephaven server (e.g., 42.6). For Gradle:
JVM configuration
The Java client transfers data with Apache Arrow, which needs reflective access to java.nio.Buffer. Add this JVM option to avoid an InaccessibleObjectException on Java 16 and later:
With Deephaven 43.0 and later on Java 25 and later, also add -Dio.netty.noUnsafe=false. Without it, Arrow's Netty allocator fails with a ClassCastException (PooledDirectByteBuf cannot be cast to ... PooledUnsafeDirectByteBuf).
The following snippets include both options. Keep -Dio.netty.noUnsafe=false on earlier Java versions too. It's harmless there because it sets the value Netty already uses.
With Gradle's application plugin, set the default JVM arguments, which apply to the run task and the generated start scripts:
To set them on one JavaExec task only, such as run, configure that task:
Authentication
By default, the Deephaven server requires pre-shared key (PSK) authentication. A Java client that sets no authentication connects as anonymous, and the server rejects it.
To authenticate, pass your key to authenticationTypeAndValue when you build the FlightSession that connects to the server. The value is io.deephaven.authentication.psk.PskAuthenticationHandler followed by a space and the key.
The complete example builds the session like this, reading the key from the DEEPHAVEN_PSK environment variable. Set that variable to your server's key before you run it.
If your server has anonymous authentication enabled, remove the authenticationTypeAndValue call.
Basic pattern
The basic pattern for sending data from a Java client is:
- Create a
FlightSessionto connect to the Deephaven server. - Define an input table spec, a client-side description of the table that doesn't exist on the server until you execute it. List the columns in a
TableHeaderbuilt fromColumnHeaders, then pass the header toInMemoryKeyBackedInputTable,InMemoryAppendOnlyInputTable, orBlinkInputTable, which Input table types compares. - Execute the spec to create the input table on the server. Execution returns a
TableHandlefor the new table. - Publish the input table to the query scope under a variable name. This makes the table visible on the server and keeps a server-side reference to it.
- Close the handle to release the server-side export it holds. The published table stays alive through the query scope.
- Build data rows using
ColumnHeaderandNewTable. - Add to the input table with
FlightSession.addToInputTable. Pass aScopeId, which refers to the published table by its query-scope variable name (for example,new ScopeId("device_status")). For keyed tables, this inserts new rows or updates existing ones.
Repeat steps 6-7 as needed. To remove rows from a keyed input table, call FlightSession.deleteFromInputTable with a table of key values instead, as shown in Delete rows from a keyed input table.
Input table types
The Java client supports three types of input tables. Each type's of method takes a table schema. The snippets below pass a TableHeader named header, which lists the column names and types. The complete example shows how to build one.
Keyed
Create a keyed input table with InMemoryKeyBackedInputTable. The key columns identify each row. Adding a row with an existing key replaces that row. You can also delete rows, as shown in Delete rows from a keyed input table.
Append-only
Create an append-only input table with InMemoryAppendOnlyInputTable. Each new row goes at the end of the table. The table has no key columns and doesn't support updates or deletion. Use this when you need to keep every row you send, such as a log or event history.
Blink
Create a blink input table with BlinkInputTable. In a blink table, rows are visible for only one update graph cycle, then disappear. You can't delete rows from a blink input table. Use this for event streams where downstream queries only need each new batch, not the history.
Complete example
The following example tracks device status using a keyed input table. Each device has a unique ID, and updates replace the previous status for that device.
Batch uploads
The complete example sends one row per call to show how each update replaces the previous row. When you have several rows ready at once, such as the three initial devices there, send them in a single upload, which is faster. This snippet uses the column headers, flight, allocator, and scopeId from the complete example:
Delete rows from a keyed input table
To delete rows from a keyed input table, call FlightSession.deleteFromInputTable with a table that contains every key column and no other columns. The server rejects a delete table that is missing a key column or includes any other column. This snippet uses the flight, allocator, and scopeId from the complete example and deletes two devices in one call:
Resource management
TableHandle lifecycle
A TableHandle refers to an export, which is a table the server keeps alive for your session until you release the handle. TableHandle implements Closeable, and closing it releases the export. A handle you never close keeps its export alive on the server until the session closes.
That is why steps 3-5 of the basic pattern publish the input table to the query scope and then close the handle. The query scope keeps the table alive, and later calls reach it through a ScopeId.
Data upload lifecycle
The FlightSession.addToInputTable method performs these steps in a single call:
- Uploads the
NewTableto the server as a temporary export that exists only for this call. - Calls the input table service to add the data.
- Releases the temporary export when complete.
If you need more control, do the three steps yourself:
- Upload the data with
FlightSession.putExportManual, which returns anExportId. - Call
Session.addToInputTablewith thatExportId. - Release the export with
FlightSession.releasewhen done.
This snippet uses the column headers, flight, allocator, and scopeId from the complete example:
Working examples
The Deephaven repository includes complete working examples:
AddToInputTable.java— Append-only input table with server-side validation. Requires a Python server, because it sets up the validator from the Python console.KeyValueInputTable.java— Keyed input table with add and delete.AddToBlinkTable.java— Blink input table.