When developing applications that use Apache Cassandra as a backend, you’ll eventually encounter situations where simple data types (like text
, int
, or timestamp
) are not sufficient to represent your domain model. This is where User-Defined Types (UDTs) come in.
In this article, we’ll break down what UDTs are, when to use them, and how to work with them in Spring Data Cassandra—complete with a real-world example involving a complex TransactionEntity
object. We’ll also show how to fix common errors such as:
org.springframework.data.mapping.MappingException: Cannot resolve DataType
No converter found capable of converting
🧠 What Is a User-Defined Type (UDT) in Cassandra?
A User-Defined Type (UDT) in Cassandra allows you to define a custom structured type made up of multiple fields. This is similar to a class or struct in programming languages and is perfect for modeling complex objects like addresses, transactions, or metadata.
For example, you can define a transaction_type
like this in CQL:
CREATE TYPE transaction_type (
transactionId text,
entryReference text,
endToEndId text,
mandateId text,
checkId text,
creditorId text,
additionalInformation text,
batchNumberOfTransactions text,
creditorName text,
ultimateCreditor text,
debtorName text,
ultimateDebtor text,
remittanceInformationUnstructured text,
remittanceInformationStructured text,
purposeCode text,
bankTransactionCode text,
proprietaryBankTransactionCode text,
bookingStatus text,
cardTransactionId text,
invoiced boolean,
transactionDetails text,
batchIndicator boolean
);
You can then embed this UDT in a Cassandra table, for example:
CREATE TABLE transactions (
consentId text,
resourceId text,
transactionId text,
iban text,
transaction frozen<transaction_type>,
PRIMARY KEY ((consentId), resourceId, transactionId)
);
🛠️ Using UDTs in Spring Data Cassandra
Let’s say you have a TransactionEntity
class in Java that looks like this:
@UserDefinedType("transaction_type")
public class TransactionEntity {
private String transactionId;
private String entryReference;
private String endToEndId;
private String mandateId;
private String checkId;
private String creditorId;
private String additionalInformation;
private String batchNumberOfTransactions;
private String creditorName;
private String ultimateCreditor;
private String debtorName;
private String ultimateDebtor;
private String remittanceInformationUnstructured;
private String remittanceInformationStructured;
private String purposeCode;
private String bankTransactionCode;
private String proprietaryBankTransactionCode;
private String bookingStatus; // For simplicity, assume it's a String
private String cardTransactionId;
private Boolean invoiced;
private String transactionDetails;
private Boolean batchIndicator;
// Getters and setters...
}
And your main entity looks like:
@Table("transactions")
public class TransactionWrapper {
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED)
private String consentId;
@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED)
private String resourceId;
@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED)
private String transactionId;
private String iban;
@CassandraType(type = CassandraType.Name.UDT, userTypeName = "transaction_type")
private TransactionEntity transaction;
// Getters and setters...
}
🧯 Common Errors and Fixes
❌ Error: Cannot resolve DataType
This error occurs when Spring Data Cassandra cannot infer the mapping for a custom object. The fix is to:
- Annotate the class with
@UserDefinedType("transaction_type")
. - Annotate the field with
@CassandraType(type = Name.UDT, userTypeName = "transaction_type")
.
❌ Error: No converter found capable of converting
This means Spring Data needs help converting the custom Java object to and from a Cassandra UDTValue
.
You fix it by writing custom converters.
✅ Implementing Custom Converters
TransactionEntityReadConverter
@ReadingConverter
public class TransactionEntityReadConverter implements Converter<UDTValue, TransactionEntity> {
@Override
public TransactionEntity convert(UDTValue source) {
TransactionEntity tx = new TransactionEntity();
tx.setTransactionId(source.getString("transactionId"));
tx.setEntryReference(source.getString("entryReference"));
// ... all other fields ...
tx.setBatchIndicator(source.getBool("batchIndicator"));
return tx;
}
}
TransactionEntityWriteConverter
@WritingConverter
public class TransactionEntityWriteConverter implements Converter<TransactionEntity, UDTValue> {
private final UserType userType;
public TransactionEntityWriteConverter(UserType userType) {
this.userType = userType;
}
@Override
public UDTValue convert(TransactionEntity tx) {
return userType.newValue()
.setString("transactionId", tx.getTransactionId())
.setString("entryReference", tx.getEntryReference())
// ... all other fields ...
.setBool("batchIndicator", tx.getBatchIndicator());
}
}
🧩 Registering Converters in Spring
@Configuration
public class CassandraConfig {
@Autowired
private MappingContext<?, ?> mappingContext;
@Bean
public CassandraCustomConversions cassandraCustomConversions() {
UserType transactionUDT = ((CassandraMappingContext) mappingContext)
.getUserTypeResolver()
.resolveType("transaction_type");
return new CassandraCustomConversions(Arrays.asList(
new TransactionEntityReadConverter(),
new TransactionEntityWriteConverter(transactionUDT)
));
}
}
🧪 Final Tips for Working with UDTs in Spring Data Cassandra
- Always synchronize your Cassandra UDT definitions and your Java model.
- Use
@UserDefinedType
and@CassandraType
to explicitly declare mappings. - Write custom converters when you get serialization/deserialization errors.
- Register those converters in a Spring
@Configuration
class.
🚀 Conclusion
User-Defined Types (UDTs) in Cassandra allow you to model rich, structured data in a NoSQL world. When using Spring Data Cassandra, UDTs make it easy to persist and retrieve nested domain objects like TransactionEntity
. If you follow the correct annotations and register custom converters when needed, you can avoid common pitfalls and ensure seamless integration.
Whether you’re building an open banking platform, a payment processor, or any app with structured domain logic—UDTs are your friend in Cassandra.
Would you like a featured image and a list of SEO tags to go with this article?