email iconemail phone iconcall

Introducing DataStax Java Driver 2.1

By Olivier Michallat -  August 26, 2014 | 10 Comments

We are pleased to announce the latest version of our Java driver, released in time for Cassandra 2.1.

This release brings support for Cassandra 2.1, while remaining compatible with 1.2 and 2.0. It also introduces a new object mapping API, which simplifies the conversion of query results to custom Java classes. Finally, it includes several improvements (for a full list, refer to the changelog).

Without further ado, let's see these new features in action:

User Defined Types and tuples

As previously described on this blog, Cassandra 2.1 introduces User Defined Types, which are named groups of related properties:

CREATE TYPE address (
    street text,
    city text,
    zip int

CREATE TABLE user_profiles (
    email text PRIMARY KEY,
    address address

From the driver's perspective, UDT values can be retrieved like any other type. They are implemented as a map-like object, with the usual getters and setters:

Row row = session.execute("SELECT * FROM user_profiles").one();
UDTValue address = row.getUDTValue("address");

String street = address.getString("street"); // by field name
int zip       = address.getInt(2);           // by index

You can also get hold of the data type representing a particular UDT; it is useful when you want to create new values:

// Get the type from an existing value:
UserType addressType = address.getType();
// Or from the cluster metadata:
UserType addressType = cluster.getMetadata().getKeyspace("ks").getUserType("address");

UDTValue address2 = addressType.newValue()
                               .setString("street", "1600 Pennsylvania Ave NW")
                               .setString("city", "Washington")
                               .setInt("zip", 20500);

session.execute("INSERT INTO user_profiles (email, address) VALUES (?, ?)",
                "", address2);

One thing that immediately comes to mind is that we'd rather use our own Address class in our code. This is exactly what the object mapper is here for, as we'll find out shortly.

Also new in Cassandra 2.1, tuples are essentially anonymous UDTs: collections of unnamed fields with predefined types.

CREATE TABLE points_of_interest (
    id int PRIMARY KEY,
    name text,
    coordinates tuple<float,float>

Querying works mostly like UDTs, except that fields can only be accessed by index:

Row row = session.execute("SELECT * FROM points_of_interest").one();
TupleValue coordinates = row.getTupleValue("coordinates");
float latitude  = coordinates.getFloat(0);
float longitude = coordinates.getFloat(1);

New tuple values can be created directly based on the types of the field:

TupleType coordinatesType = TupleType.of(DataType.cfloat(), DataType.cfloat());
TupleValue newCoordinates = coordinatesType.newValue(48.858222F, 2.2945F);

Simple object mapper

Most Java applications use custom Java classes to represent their data (for example UserProfile and Address in our first example). Converting back and forth between those classes and the driver's own types (Row and TupleValue) involves some boilerplate code and can be automated.

The goal of the object mapper is to generate most of that boilerplate for you. To specify the target tables and UDTs in Cassandra, decorate your Java classes with annotations:

@UDT(keyspace = "ks", name = "address")
public class Address {
    private String street;
    private String city;
    private int zip;

    // getters and setters omitted...

@Table(keyspace = "ks", name = "user_profiles")
public class UserProfile {
    private String email;
    private Address address;

    // getters and setters omitted...

You can then retrieve a mapper that handles basic CRUD operations (which come in both synchronous and asynchronous flavors):

MappingManager manager = new MappingManager(session);
Mapper mapper = manager.mapper(UserProfile.class);

UserProfile myProfile = mapper.get("");
ListenableFuture saveFuture = mapper.saveAsync(anotherProfile);

For more complex queries, the mapper can also generate an "accessor" object from an interface annotated with the queries to perform:

interface ProfileAccessor {
    @Query("SELECT * FROM user_profiles LIMIT :max")
    Result firstN(@Param("max") int limit);

ProfileAccessor accessor = manager.createAccessor(ProfileAccessor.class);
Result profiles = accessor.firstN(10);

// Result is like ResultSet, but specialized for a mapped class:
for (UserProfile profile : profiles) {

The object mapper is deliberately simple: its primary goal is to replace boilerplate code, not to hide Cassandra from the developer. Therefore it avoids complex features like lazy-loading or entity proxies.

Upgrading to 2.1

This new version of the driver is available from the Maven repositories (note that the object mapper is published as a separate artifact), and as a packaged binary. Refer to the upgrade guide if you are upgrading from a previous version.

While we strive to preserve backwards-compatibility, version 2.1 introduces a few internal API changes that will be transparent to most users, and two user API changes (all documented here).

Thank you for using and supporting Apache Cassandra and DataStax.

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

get certified

DBA's Guide to NoSQL


  1. Ziju says:

    Is prepared statement used in the backgrond? What’s the performance overhead of mapper/accessor? How to specify composite partition keys and compound keys?

    1. Olivier Michallat says:

      Yes, prepared statements are used.

      We’ve not run benchmarks for the mapper yet, but it’s designed to have a very low profile. Most of the work is done when initializing the Mapper instances, and then cached at the MappingManager level.

      You specify composite partition keys and compound keys with the @PartitionKey and @ClusteringColumn annotations.

      1. Ziju says:

        Thanks for your quick reply.

        Since most of work is done during initialization, should we only initialize the mapper/accessor instances once and use them during application’s lifetime?

        Another question is how the conversion from Java POJOs to text is handled by the mapper? The example listed in shows that the field “phones” in Address instances is stored as list in the UDT type. What protocol does the mapper use to convert Phone instances to text?


        1. Ziju says:

          I mean “phones” is stored as “list”.

        2. Ziju says:

          I wanted to post list《text》 but the “text” part seemed to be treated as a html tag and omitted.

      2. If you are using @PartitionKey to specify composite keys, how do you specify the ordinality?

  2. Joseph says:

    I have tried your Mapper and User defined types both are not working and getting some issue. I am using cassandra-java-driver-2.1.2.

    Address address = new Address();
    address.setStreet(“25800 Arnold Drive”);
    System.out.println(“address has been created”);
    Account account = new Account(“John Doe”, “”, address);
    System.out.println(“account has been created”);

    Mapper mapper = new MappingManager(session).mapper(Account.class);
    System.out.println(“mapper has been created”);
    //Phone phone = new Phone(“home”, “707-555-3537”);
    //List phones = new ArrayList();

    mapper.saveAsync(account); //save(account);
    System.out.println(“save has been completed.”);
    Account whose = mapper.get(“”);
    System.out.println(“Account name: ” + whose.getName());

    @UDT(keyspace=”complex”, name=”address”)
    public class Address {
    private String street;
    private String city;
    private int zipCode;
    public String getStreet() { return street; }
    public void setStreet(String street) { this.street = street; }
    public String getCity() { return city; }
    public void setCity(String city) { = city;}
    public int getZipCode() {return zipCode;}
    public void setZipCode(int zipCode) {this.zipCode = zipCode;} }


    address has been created
    account has been created
    mapper has been created
    java.lang.IllegalArgumentException: “street” is not a field defined in this UDT
    at com.datastax.driver.core.UDTValue.getAllIndexesOf(
    at com.datastax.driver.core.AbstractData.setBytesUnsafe(
    at com.datastax.driver.mapping.UDTMapper.toUDT(
    at com.datastax.driver.mapping.ReflectionMapper$UDTColumnMapper.getValue(
    at com.datastax.driver.mapping.Mapper.saveQuery(
    at com.datastax.driver.mapping.Mapper.saveAsync(
    at com.att.opus.mytest.AccountMapper.test(
    at com.att.poc.Main.accountTest(
    at com.att.poc.Main.main(
    End Main

  3. vinod says:

    While using @Accessor, For select query, Can you point me to an example to filter columns in query itself instead of select *

    For example

    Select column_A from table

  4. amit says:

    CREATE TABLE mykeyspace.users (
    id uuid PRIMARY KEY,
    name frozen ,
    direct_reports set<frozen >, // a collection set
    addresses map<text, frozen > // a collection map
    score set<frozen <set>> // a set with a nested frozen set

    from above table how can i read data from
    addresses map<text, frozen > using java code

  5. amit says:

    Column_Name map<uuid, Frozen>

    I want to read data from this column how can i do it using java


Your email address will not be published. Required fields are marked *

Subscribe for newsletter: