DataStax Java Driver: 3.0.0 released!
It's finally here! The Java driver team is pleased to announce that the long-awaited 3.0.0 version has just been released.
With the switch to using semantic versioning, we seized the opportunity of this major release to clean up the API; as a consequence, version 3.0.0 is not binary compatible with older versions and has breaking changes – all of them documented in the upgrade guide (we strongly suggest reviewing it before upgrading the driver).
- Compatibility with Cassandra 2.2 and 3.0+
- Custom Codecs
- Other Major Improvements
- Getting the driver
Compatibility with Cassandra 2.2 and 3.0+
Support for new CQL types
getByte() / setByte(byte)for
getShort() / setShort(short)for
getTime() / setTime(long)for
getDate() / setDate(LocalDate)for
Note that to remain consistent with CQL type names, the methods to retrieve and set
TIMESTAMP values have been renamed to
setTimestamp(). They were formerly named
setDate(), but these now represent the
TINYINT are respectively 16 and 8-bit integers, so their usage should be quite straightforward.
DATE represents a day with no corresponding time value; it is encoded as a 32-bit unsigned integer representing a number of days, with «the Epoch» (January 1st 1970) at the center of the range (231).
TIME is the time of the day (with no specific date); it is encoded as a 64-bit signed integer representing the number of nanoseconds since midnight.
Here is a small example of how to use
session.execute("CREATE TABLE IF NOT EXISTS small_ints(s smallint PRIMARY KEY, t tinyint)"); PreparedStatement pst = session.prepare("INSERT INTO small_ints (s, t) VALUES (:s, :t)"); session.execute(pst.bind(Short.MIN_VALUE, Byte.MAX_VALUE)); Row row = session.execute("SELECT * FROM small_ints").one(); short s = row.getShort("s"); byte t = row.getByte("t");
There is one minor catch: Java's integer literals default to
int, which the driver serializes as CQL
INTs. So the following will fail:
session.execute(pst.bind(1, 1)); // InvalidTypeException: Invalid type for value 0 of CQL type smallint, // expecting class java.lang.Short but class java.lang.Integer provided
The workaround is simply to coerce your arguments to the correct type:
And here is a small example of all 3 CQL temporal types,
session.execute("CREATE TABLE IF NOT EXISTS dates(ts timestamp PRIMARY KEY, d date, t time)"); session.execute("INSERT INTO dates (ts, d, t) VALUES ('2015-01-28 11:47:58', '2015-01-28', '11:47:58')"); Row row = session.execute("SELECT * FROM dates").one(); Date ts = row.getTimestamp("ts"); LocalDate d = row.getDate("d"); long t = row.getTime("t");
As you see,
TIMESTAMP is still mapped to
TIME is mapped by the driver to primitive longs, representing the number of nanoseconds since midnight. As for
DATE values, the driver encapsulates them in a new class, LocalDate. As it can be quite cumbersome to work with raw
DATE literals (specially because Java doesn't have unsigned integers), the
LocalDate class aims to hide all that complexity behind utility methods to convert
LocalDate instances to and from integers representing the number of days since the Epoch.
Should the driver's default mappings for temporal types not suit your needs, we have good news: the new "extras" module – see below – contains alternative codecs to deal with
- A LocalDateCodec that maps
DATEto Java 8 LocalDate;
- Another LocalDateCodec that maps
DATEto Joda Time LocalDate;
- A LocalTimeCodec that maps
TIMEto Java 8 LocalTime;
- Another LocalTimeCodec that maps
TIMEto Joda Time LocalTime.
And for those who prefer to keep it low-level and avoid the overhead of creating container classes:
- SimpleDateCodec maps
DATEto primitive ints representing the number of days since the Epoch; and
- SimpleTimestampCodec maps
TIMESTAMPto primitive longs representing milliseconds since the Epoch.
For Protocol V3 or below, all variables in a statement must be bound. With Protocol V4, variables can be left "unset", in which case they will be ignored server-side (no tombstones will be generated). If you’re reusing a bound statement you can use the unset methods to unset variables that were previously set:
BoundStatement bound = ps1.bind().setString("foo", "bar"); // Unset by name bound.unset("foo"); // Unset by index bound.unset(0);
Note that this will not work under lower protocol versions; attempting to do so would result in an
IllegalStateException urging you to explicitly set all values in your statement.
Changes to Schema Metadata API
As you probably already know, CASSANDRA-6717 has completely changed the way Cassandra internally stores information about schemas, while CASSANDRA-6477 introduced Materialized Views, and CASSANDRA-7395 introduced User-defined Functions and Aggregates. On top of that, secondary indexes have been deeply refactored by CASSANDRA-9459.
The driver now fully supports all these features and changes; let's see how.
Retrieving metadata on a materialized view is straightforward:
MaterializedViewMetadata mv = cluster.getMetadata() .getKeyspace("test").getMaterializedView("my_view");
Alternatively, you can obtain the view form its parent table:
TableMetadata table = cluster.getMetadata() .getKeyspace("test").getTable("my_table") MaterializedViewMetadata mv = table.getView("my_view"); // You can also query all views in that table: System.out.printf("Table %s has the following views: %s%n", table.getName(), table.getViews());
To illustrate the driver's support for user-defined functions and aggregates, let's consider the following example:
USE test; CREATE FUNCTION plus(x int, y int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return x + y;'; CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0;
To retrieve metadata on the function defined above:
FunctionMetadata plus = cluster.getMetadata() .getKeyspace(keyspace) .getFunction("plus", DataType.cint(), DataType.cint()); System.out.printf("Function %s has signature %s and body '%s'%n", plus.getSimpleName(), plus.getSignature(), plus.getBody());
To retrieve metadata on the aggregate defined above:
AggregateMetadata sum = cluster.getMetadata() .getKeyspace(keyspace) .getAggregate("sum", DataType.cint()); System.out.printf("%s is an aggregate that computes a result of type %s%n", sum.getSimpleName(), sum.getReturnType()); FunctionMetadata plus = sum.getStateFunc(); System.out.printf("%s is a function that operates on %s%n", plus.getSimpleName(), plus.getArguments());
Note that, in order to retrieve a function or aggregate from a keyspace, you need to specify its name and its argument types, to distinguish between overloaded versions.
The way to retrieve metadata on a secondary index has changed from 2.1: the former one-to-one relationship between a column and its (only) index has been replaced with a one-to-many relationship between a table and its many indexes. This is reflected in the driver's API by the new methods
TableMetadata table = cluster.getMetadata() .getKeyspace("test") .getTable("my_table"); IndexMetadata index = table.getIndex("my_index"); System.out.printf("Table %s has index %s targeting %s%n", table.getName(), index.getName(), index.getTarget());
To retrieve the column an index operates on, you should now inspect the result of the
IndexMetadata index = table.getIndex("my_index"); ColumnMetadata indexedColumn = table.getColumn(index.getTarget()); System.out.printf("Index %s operates on column %s%n", index.getName(), indexedColumn);
Beware however that the code above only works for built-in indexes where the index target is a single column name. If in doubt, make sure to read the upgrade guide should you need to migrate existing code.
With Protocol V4, do not miss anymore the oracles emitted by Cassandra!
Joking aside, Cassandra can now send warnings along with the server response; these can include useful information such as batches being too large, too many tombstones being read, etc.. With the Java driver, you can retrieve them by simply inspecting the
ResultSet rs = session.execute(...); List<String> warnings = rs.getExecutionInfo().getWarnings();
New Exception Types
Also, note that thanks to JAVA-1006, the whole exceptions hierarchy has been redesigned in this version.
And finally, Custom payloads are generic key-value maps that can be sent alongside a query. They are used to convey additional metadata when you deploy a custom query handler on the server side.
In short, where before the driver had a hard-coded set of mappings between CQL types and Java types, now it has a fully dynamic, pluggable and customizable mechanism of handling CQL-to-Java conversions.
With custom codecs, users can now define their own mappings, and the driver will use them wherever appropriate, seamlessly. The possibilities are endless: map CQL temporal types to Java 8 Time API or to Joda Time, as we already mentioned; provide transparent JSON-to-Java and XML-to-Java conversion; map CQL collections to Java arrays or – why not? – to Scala collections...
Just to give you a hint of how powerful this feature can be, imagine that you developed your own codec to convert JSON strings stored in Cassandra to Java objects; the code to retrieve your objects would be as simple as this:
// Roll your own codec TypeCodec<MyPojo> myJsonCodec = ...; // register it so the driver can use it cluster.getConfiguration().getCodecRegistry().register(myJsonCodec); // query some JSON data Row row = session.execute("SELECT json FROM t WHERE pk = 'I CAN HAZ JSON'").one(); // Let the driver convert it for you... MyPojo myPojo = row.get("json", MyPojo.class);
Custom codecs gave us the opportunity to introduce a new member in the Java driver family: the "extras" module.
This module has been created to host additions to the driver that, albeit useful, cannot make into the core API, mainly for backwards-compatibility reasons, or because they target a more specific audience (e.g. they require Java 8 or higher, while the driver must remain compatible with older versions of Java).
To use this new module in your own application, simply pull the following Maven dependency:
<dependency> <groupId>com.datastax.cassandra</groupId> <artifactId>cassandra-driver-extras</artifactId> <version>3.0.0</version> </dependency>
To celebrate the event, we included in this version a rich set of codecs that we hope will be useful to many of you:
- Codecs for Java arrays;
- Codecs for Java enums;
- Codecs for Java 8 types;
- Codecs for Joda Time;
- Codecs for JSON (using Jackson or JSR-353);
- ...and many more!
Check our online documentation for more details.
Note that the mapping framework has been retrofitted to use custom codecs when appropriate. One of the consequences of that is that the
@Enumerated annotation has gone, replaced with codecs from the extras module. Again, please read the upgrade guide for more details if you need to migrate existing code.
Other Major Improvements
- On a client timeout, while waiting for the server response;
- On a connection error (socket closed, etc.);
- When the contacted host replies with an unusual error, such as
To distinguish among these error cases, one should inspect the
DriverException that is passed to the method call. Here is a summary of the possible situations:
|Timeout (no server response)||
|Network failure (socket closed, etc.)||
Until now, the driver had a hardcoded behavior for all these cases: retry the query. But this behavior is actually dangerous if the query being executed is not idempotent; from now on, users can override the default behavior if they need to. And to make their lives even easier, the driver provides the new
IdempotenceAwareRetryPolicy, that conveniently decorates any existing
RetryPolicy with idempotence awareness, based on the idempotence flag; here is an example:
Cluster cluster = Cluster.builder() .addContactPoints("127.0.0.1") // by default, statements will be considered non-idempotent .withQueryOptions(new QueryOptions().setDefaultIdempotence(false)) // make your retry policy idempotence-aware .withRetryPolicy(new IdempotenceAwareRetryPolicy(DefaultRetryPolicy.INSTANCE)) .build(); Session session = cluster.connect(); // by default, statements like this one will not be retried session.execute("INSERT INTO table (pk, c1) VALUES (42, 'foo')"); // but this one will session.execute(new SimpleStatement("SELECT c1 FROM table WHERE pk = 42").setIdempotent(true));
Named parameters in
// Note the use of named parameters in the query String query = "SELECT * FROM measures WHERE sensor_id=:sensor_id AND day=:day"; Map<String, Object> params = new HashMap<String, Object>(); params.put("sensor_id", 42); params.put("day", "2016-01-28"); SimpleStatement statement = new SimpleStatement(query, params);
One caveat though: named parameters were introduced in Protocol V3, and thus require Cassandra 2.1 or higher. Check our online documentation on simple statements for more information.
Per-statement read timeouts
With JAVA-1033, you now have the possibility to specify read timeouts (i.e. the amount of time the driver will wait for a response before giving up) on a per-statement basis: the new method
Statement.setReadTimeoutMillis() overrides the default per-host read timeout defined by
This can be useful for statements that are granted longer timeouts server-side (for example, aggregation queries). Again, our online documentation on socket options has more about this.
Additions to the
getBroadcastAddress()returns the node's broadcast address. This corresponds to the
broadcast_addresssetting in cassandra.yaml.
getListenAddress()returns the node's listen address. This corresponds to the
listen_addresssetting in cassandra.yaml.
getDseVersion()returns the DSE version the host is running (when applicable).
getDseWorkload()returns the DSE workload the host is running (when applicable).
Note however that these methods are provided for informational purposes only; depending on the cluster version, on the cluster type (DSE or not), and on the host the information has been fetched from, they may return
null at any time.
Getting the driver
We're also running a platform and runtime survey to improve our testing infrastructure. Your feedback would be most appreciated.
DataStax has many ways for you to advance in your career and knowledge.
You can take free classes, get certified, or read one of our many white papers.
register for classes
DBA's Guide to NoSQL