Skip to content
Bluetape4k docs1.11

Map rows and Cassandra values into Kotlin types

Latest stable Based on Bluetape4k release 1.11.0

When query columns are stable, convert each Row at the read boundary instead of carrying a Map<String, Any?> through the application. One mapper then owns column names, null decisions, and collection-copying policy, and schema mismatches fail close to the query.

import com.datastax.oss.driver.api.core.cql.Row
import io.bluetape4k.cassandra.data.getList
data class User(
val id: Long,
val name: String,
val tags: List<String>,
)
fun Row.toUser(): User = User(
id = getLong("id"),
name = getString("name") ?: error("users.name must not be null"),
tags = getList<String>("tags")?.toList().orEmpty(),
)

The mapper records that name is required. It treats null tags as an empty collection and copies the mutable list returned by the driver with toList(). Keep the property nullable instead of using orEmpty() when null and an empty collection have different domain meanings.

RowSupport can expose columns as maps keyed by index, rendered name, or CqlIdentifier.

APIKeySuitable use
toMap()Column indexDiagnostics that need result order
toNamedMap()Rendered CQL nameDynamic export, diagnostic summaries, schema exploration
toCqlIdentifierMap()CqlIdentifierDynamic processing that must preserve quoting and case rules
map, mapWithName, mapWithCqlIdentifierSame as aboveApply one transformation at a dynamic boundary
columnCodecs()CqlIdentifierInspect the TypeCodec selected by the driver

These functions decode each column with the driver codec selected for its type. toNamedMap() sorts entries by rendered CQL name, so it does not preserve select-list order. The non-transforming toMap(), toNamedMap(), and toCqlIdentifierMap() functions use Any? values, which means a misspelled field or wrong expected type is not caught at compile time. The map* functions instead return the transform result type T.

Use map conversion for diagnostics or boundaries whose columns are only known at runtime. Prefer an explicit mapper such as toUser() for a stable domain model. Do not turn a complete row into a map and log it when the result can contain sensitive columns.

Decide whether null and empty string are equivalent

Section titled “Decide whether null and empty string are equivalent”

getStringOrEmpty(index|name|id) applies orEmpty() to the nullable value returned by the driver. It therefore maps both Cassandra null and an empty string to "".

import io.bluetape4k.cassandra.cql.getStringOrEmpty
val displayName = row.getStringOrEmpty("display_name")
// Preserve the nullable getter when null and empty have different meanings.
val middleName: String? = row.getString("middle_name")

Use getStringOrEmpty only when the consumer intentionally treats both states alike, such as a display fallback. Keep the nullable getter when absence affects validation, updates, or audit behavior.

GettableSupport adds Kotlin reified helpers to the Java Driver’s GettableByName, GettableByIndex, and GettableById interfaces. Each form provides getValue, getList, getSet, and getMap; the session codec registry still performs the actual decoding.

import com.datastax.oss.driver.api.core.CqlIdentifier
import io.bluetape4k.cassandra.data.getList
import io.bluetape4k.cassandra.data.getValue
val name: String? = row.getValue("name")
val tags: MutableList<String>? = row.getList("tags")
val nameId = CqlIdentifier.fromInternal("name")
val sameName: String? = row.getValue(nameId)

These helpers do not let the compiler prove that a requested Kotlin type matches the Cassandra column. Reading fails through the driver when no suitable codec exists or the requested type is incompatible. Prefer names in ordinary application mapping, CqlIdentifier when metadata or quoted identifiers matter, and indexes only inside narrow functions that deliberately depend on select-list order.

SettableSupport adds setValue, setList, setSet, and setMap to the settable interfaces implemented by values such as BoundStatement, UdtValue, and TupleValue. Name-based setValue accepts a nullable value, while the collection helpers pass reified element types to the driver.

import io.bluetape4k.cassandra.data.setList
import io.bluetape4k.cassandra.data.setValue
val bound = prepared.boundStatementBuilder()
.setValue("name", user.name)
.setList("tags", user.tags)
.build()

The helpers do not interpolate values into CQL. Define bind markers in a prepared statement, then set values with names and Cassandra types that match that statement. Whether null should be bound or omitted remains a mapper or call-site decision.

For a UDT, find its UserDefinedType in keyspace metadata and create a value with newValue. Define a tuple with DataTypes.tupleOf(...) and create values from that type. Read them with the Java Driver’s getUdtValue and getTupleValue methods.

val coordinatesType = session.metadata
.getKeyspace("examples")
.flatMap { keyspace -> keyspace.getUserDefinedType("coordinates") }
.orElseThrow(::IllegalArgumentException)
val coordinates = coordinatesType.newValue(12, 34)
val bound = prepared.bind().setUdtValue("coordinates", coordinates)
val stored = row.getUdtValue("coordinates")
val x = stored?.getInt("x")

Tuple values use the same pattern with TupleValue and setTupleValue/getTupleValue. The bluetape4k helpers do not replace this driver model with a different serialization format. Schema and call-site code still own UDT field names and order, tuple element order, and nullability.

Register types not covered by built-in codecs with CqlSessionBuilder.addTypeCodecs(...). The 1.11.0 examples register MappingCodec, enum, Optional, and array codecs before using getValue<T> and setValue.

sessionBuilder.addTypeCodecs(CqlIntToStringCodec())
val bound = prepared.bind().setValue("pk", "1")
val pk: String? = row.getValue("pk")

Calling getValue<String> does not by itself turn a Cassandra int into a string. A TypeCodec connecting those CQL and Kotlin types must be registered in that session. Treat codec selection rules as session configuration when more than one codec can handle the same pair.

If the DataStax Mapper generated an EntityHelper<T>, bluetape4k mapper helpers can remove repetitive preparation and binding code.

import io.bluetape4k.cassandra.mapper.bind
import io.bluetape4k.cassandra.mapper.prepareInsert
val prepared = userEntityHelper.prepareInsert(session)
val statement = userEntityHelper.bind(prepared, user)
val result = session.execute(statement)

prepareInsert and prepareInsertIfNotExists pass CQL produced by the EntityHelper to CqlSession.prepare. bind fills a prepared-statement builder from the entity and returns a BoundStatement. None of these functions executes the statement; execution remains the caller’s responsibility.

The bind defaults are NullSavingStrategy.DO_NOT_SET and lenient = true. DO_NOT_SET does not call a setter for a null property, leaving its bind marker unset; in an UPDATE, an existing column value is therefore not overwritten. SET_TO_NULL binds null properties as CQL NULL.

With lenient = true, entity properties without matching target columns are skipped, so the statement may be only partially populated. With lenient = false, every non-computed entity property must have a matching target column or binding throws IllegalArgumentException. Choose both arguments after checking the intended write and the prepared statement’s bind markers.

bluetape4k-cassandra 1.11.0 exposes the DataStax mapper runtime as an API dependency. An annotation processor must still generate the application’s EntityHelper<T> code. A runtime dependency on its own does not create helpers. Without processor configuration, an explicit typed row mapper is often the simpler boundary.

SituationChoose
Stable query columnsAn explicit Row-to-domain mapping function
Columns selected at runtimetoNamedMap() or toCqlIdentifierMap()
Read or write collections by name, index, or identifierReified getter/setter helpers
Work directly with Cassandra UDT or Tuple valuesDriver UdtValue and TupleValue APIs
Connect a separate Kotlin type to a CQL typeA session-registered TypeCodec
DataStax Mapper generated an entity helperEntityHelper prepare/bind helpers

Avoid making Any? maps the default for a fixed schema, collapsing null into empty values without a domain decision, or requesting arbitrary types without a registered codec. A short, explicit conversion boundary exposes column and type errors close to the query.