The core module handles cluster connectivity and request execution. It is published under the following coordinates:
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>java-driver-core</artifactId>
<version>${driver.version}</version>
</dependency>
(For more details on setting up your build tool, see the integration page.)
Here's a short program that connects to Cassandra and executes a query:
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.*;
try (CqlSession session = CqlSession.builder().build()) { // (1)
ResultSet rs = session.execute("select release_version from system.local"); // (2)
Row row = rs.one();
System.out.println(row.getString("release_version")); // (3)
}
- CqlSession is the main entry point of the driver. It holds the known state of the actual Cassandra cluster, and is what you use to execute queries. It is thread-safe, you should create a single instance (per target Cassandra cluster), and share it throughout your application;
- we use
execute
to send a query to Cassandra. This returns a ResultSet, which is an iterable of Row objects. On the next line, we extract the first row (which is the only one in this case); - we extract the value of the first (and only) column from the row.
Always close the CqlSession
once you're done with it, in order to free underlying resources (TCP
connections, thread pools...). In this simple example, we can use a try-with-resources block because
CqlSession
implements java.lang.AutoCloseable
; in a real application, you'll probably call one
of the close methods (close
, closeAsync
, forceCloseAsync
) explicitly.
This example uses the synchronous API. Most methods have asynchronous equivalents (look for *Async
variants that return a CompletionStage
).
CqlSession#builder() provides a fluent API to create an instance programmatically. Most of the customization is done through the driver configuration (refer to the corresponding section of this manual for full details).
We recommend that you take a look at the reference configuration for the list of available options, and cross-reference with the sub-sections in this manual for more explanations.
By default, CqlSession.builder().build()
fails immediately if the cluster is not available. If you
want to retry instead, you can set the reconnect-on-init option in the
configuration.
If you don't specify any contact point, the driver defaults to 127.0.0.1:9042
:
CqlSession session = CqlSession.builder().build();
This is fine for a quick start on a developer workstation, but you'll quickly want to provide specific addresses. There are two ways to do this:
- via SessionBuilder.addContactPoint() or SessionBuilder.addContactPoints();
- in the configuration via the
basic.contact-points
option.
As soon as there are explicit contact points, you also need to provide the name of the local
datacenter. All contact points must belong to it (as reported in their system tables:
system.local.data_center
and system.peers.data_center
). Again this can be specified either:
- via SessionBuilder.withLocalDatacenter();
- in the configuration via the
basic.load-balancing-policy.local-datacenter
option.
Here is a full programmatic example:
CqlSession session = CqlSession.builder()
.addContactPoint(new InetSocketAddress("1.2.3.4", 9042))
.addContactPoint(new InetSocketAddress("5.6.7.8", 9042))
.withLocalDatacenter("datacenter1")
.build();
And a full configuration example:
// Add `application.conf` to your classpath with the following contents:
datastax-java-driver {
basic {
contact-points = [ "1.2.3.4:9042", "5.6.7.8:9042" ]
load-balancing-policy.local-datacenter = datacenter1
}
}
For more details about the local datacenter, refer to the load balancing policy section.
By default, a session isn't tied to any specific keyspace. You'll need to prefix table names in your queries:
session.execute("SELECT * FROM my_keyspace.my_table WHERE id = 1");
You can also specify a keyspace at construction time, either through the configuration:
datastax-java-driver {
basic.session-keyspace = my_keyspace
}
Or with the builder:
CqlSession session = CqlSession.builder()
.withKeyspace(CqlIdentifier.fromCql("my_keyspace"))
.build();
That keyspace will be used as the default when table names are not qualified:
session.execute("SELECT * FROM my_table WHERE id = 1");
session.execute("SELECT * FROM other_keyspace.other_table WHERE id = 1");
You might be tempted to open a separate session for each keyspace used in your application; however, connection pools are created at the session level, so each new session will consume additional system resources:
// Anti-pattern: creating two sessions doubles the number of TCP connections opened by the driver
CqlSession session1 = CqlSession.builder().withKeyspace(CqlIdentifier.fromCql("ks1")).build();
CqlSession session2 = CqlSession.builder().withKeyspace(CqlIdentifier.fromCql("ks2")).build();
If you issue a USE
statement, it will change the default keyspace on that session:
CqlSession session = CqlSession.builder().build();
// No default keyspace set, need to prefix:
session.execute("SELECT * FROM my_keyspace.my_table WHERE id = 1");
session.execute("USE my_keyspace");
// Now the keyspace is set, unqualified query works:
session.execute("SELECT * FROM my_table WHERE id = 1");
Be very careful though: switching the keyspace at runtime is inherently thread-unsafe, so if the session is shared by multiple threads (and is usually is), it could easily cause unexpected query failures.
Finally, if you're connecting to Cassandra 4 or above, you can specify the keyspace independently for each request:
CqlSession session = CqlSession.builder().build();
session.execute(
SimpleStatement.newInstance("SELECT * FROM my_table WHERE id = 1")
.setKeyspace(CqlIdentifier.fromCql("my_keyspace")));
You run queries with the session's execute*
methods:
ResultSet rs = session.execute("SELECT release_version FROM system.local");
As shown here, the simplest form is to pass a query string directly. You can also pass a Statement instance.
Executing a query produces a ResultSet, which is an iterable of Row. The basic way to process all rows is to use Java's for-each loop:
for (Row row : rs) {
// process the row
}
This will return all results without limit (even though the driver might use multiple queries in
the background). To handle large result sets, you might want to use a LIMIT
clause in your CQL
query, or use one of the techniques described in the paging documentation.
When you know that there is only one row (or are only interested in the first one), the driver provides a convenience method:
Row row = rs.one();
Row provides getters to extract column values; they can be either positional or named:
Row row = session.execute("SELECT first_name, last_name FROM users WHERE id = 1").one();
// The two are equivalent:
String firstName = row.getString(0);
String firstName = row.getString(CqlIdentifier.fromCql("first_name"));
CqlIdentifier is a string wrapper that deals with case-sensitivity. If you don't want to create an instance for each getter call, the driver also provides convenience methods that take a raw string:
String firstName = row.getString("first_name");
See AccessibleByName for an explanation of the conversion rules.
CQL3 data type | Getter name | Java type | See also |
---|---|---|---|
ascii | getString | java.lang.String | |
bigint | getLong | long | |
blob | getByteBuffer | java.nio.ByteBuffer | |
boolean | getBoolean | boolean | |
counter | getLong | long | |
date | getLocalDate | java.time.LocalDate | Temporal types |
decimal | getBigDecimal | java.math.BigDecimal | |
double | getDouble | double | |
duration | getCqlDuration | CqlDuration | Temporal types |
float | getFloat | float | |
inet | getInetAddress | java.net.InetAddress | |
int | getInt | int | |
list | getList | java.util.List | |
map | getMap | java.util.Map<K, V> | |
set | getSet | java.util.Set | |
smallint | getShort | short | |
text | getString | java.lang.String | |
time | getLocalTime | java.time.LocalTime | Temporal types |
timestamp | getInstant | java.time.Instant | Temporal types |
timeuuid | getUuid | java.util.UUID | |
tinyint | getByte | byte | |
tuple | getTupleValue | TupleValue | Tuples |
user-defined types | getUDTValue | UDTValue | User-defined types |
uuid | getUuid | java.util.UUID | |
varchar | getString | java.lang.String | |
varint | getBigInteger | java.math.BigInteger | |
vector | getVector | CqlVector | Custom Codecs |
Sometimes the driver has to infer a CQL type from a Java type (for example when handling the values of simple statements); for those that have multiple CQL equivalents, it makes the following choices:
java.lang.String
:text
long
:bigint
java.util.UUID
:uuid
In addition to these default mappings, you can register your own types with custom codecs.
For performance reasons, the driver uses primitive Java types wherever possible (boolean
,
int
...); the CQL value NULL
is encoded as the type's default value (false
, 0
...), which can
be ambiguous. To distinguish NULL
from actual values, use isNull
:
Integer age = row.isNull("age") ? null : row.getInt("age");
To ensure type safety, collection getters are generic. You need to provide type parameters matching your CQL type when calling the methods:
// Assuming given_names is a list<text>:
List<String> givenNames = row.getList("given_names", String.class);
For nested collections, element types are generic and cannot be expressed as Java Class
instances.
Use GenericType instead:
// Assuming teams is a set<list<text>>:
GenericType<Set<List<String>>> listOfStrings = new GenericType<Set<List<String>>>() {};
Set<List<String>> teams = row.get("teams", listOfStrings);
Since generic types are anonymous inner classes, it's recommended to store them as constants in a utility class instead of re-creating them each time.
ResultSet and Row expose an API to explore the column metadata at runtime:
for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) {
System.out.printf("Column %s has type %s%n",
definition.getName(),
definition.getType());
}