If you’re working with Spring Data Cassandra and hit the dreaded ConverterNotFoundException
, you’re not alone. This guide walks you through the steps to solve the issue when mapping a custom Java object to a User-Defined Type (UDT) in Cassandra, providing practical code samples and best practices.
What Is a Cassandra UDT and Why It’s Used
A User-Defined Type (UDT) in Cassandra is a way to define structured, nested objects. For example, instead of storing three fields like mandateId
, checkId
, and creditorId
as separate columns, you can group them in a single UDT column.
In Spring Data, a matching Java class can represent this UDT using the @UserDefinedType
annotation:
@UserDefinedType("transaction")
public class TransactionEntity {
private String mandateId;
private String checkId;
private String creditorId;
// Getters and setters...
}
The Problem: ConverterNotFoundException
When trying to save an entity that includes this UDT, developers often encounter this error:
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [TransactionEntity] to type [UdtValue]
This means Spring Data Cassandra does not know how to serialize your custom object into a format that Cassandra understands (UdtValue
).
The Solution: Register Custom Converters
To fix this issue, you need to define two custom converters:
- A WritingConverter to convert the Java object into a Cassandra UDT.
- A ReadingConverter to convert the Cassandra UDT back into a Java object.
🛠 Step-by-Step: Writing and Reading Converters
1. Writing Converter – Java Object to UdtValue
@WritingConverter
public class TransactionEntityToUdtConverter implements Converter<TransactionEntity, UdtValue> {
private final UserDefinedType transactionUdt;
public TransactionEntityToUdtConverter(CqlSession session) {
this.transactionUdt = session.getMetadata()
.getKeyspace("your_keyspace")
.flatMap(ks -> ks.getUserDefinedType("transaction"))
.orElseThrow(() -> new IllegalArgumentException("UDT 'transaction' not found"));
}
@Override
public UdtValue convert(TransactionEntity source) {
return transactionUdt.newValue()
.setString("mandate_id", source.getMandateId())
.setString("check_id", source.getCheckId())
.setString("creditor_id", source.getCreditorId());
}
}
2. Reading Converter – UdtValue
to Java Object
@ReadingConverter
public class UdtToTransactionEntityConverter implements Converter<UdtValue, TransactionEntity> {
@Override
public TransactionEntity convert(UdtValue source) {
TransactionEntity entity = new TransactionEntity();
entity.setMandateId(source.getString("mandate_id"));
entity.setCheckId(source.getString("check_id"));
entity.setCreditorId(source.getString("creditor_id"));
return entity;
}
}
Register the Converters in Spring
In your Spring configuration class, register the converters:
@Configuration
public class CassandraConfig {
@Bean
public CassandraCustomConversions customConversions(CqlSession session) {
return new CassandraCustomConversions(List.of(
new TransactionEntityToUdtConverter(session),
new UdtToTransactionEntityConverter()
));
}
}
Make Sure Cassandra UDT Schema Matches
Ensure your Cassandra UDT is defined as:
CREATE TYPE your_keyspace.transaction (
mandate_id text,
check_id text,
creditor_id text
);
Field names must match those used in the converters (case-sensitive by default).
Testing the Setup
Use your repository to save and retrieve objects:
AccountTransaction tx = new AccountTransaction();
tx.setTransaction(new TransactionEntity("M123", "CHK789", "CRD456"));
repository.save(tx);
TransactionEntity t = repository.findById(...).get().getTransaction();
System.out.println(t.getMandateId()); // Should print "M123"
Alternative Approaches
If you prefer a less manual route:
- Use Spring’s automatic mapping by matching UDT and Java field names exactly.
- Avoid custom converters if default mapping suffices.
- Use a custom
TypeCodec
if you prefer registering conversions at the driver level instead of Spring.
Conclusion
The ConverterNotFoundException
in Spring Data Cassandra typically appears when dealing with UDTs and custom objects. By registering a pair of converters and ensuring the Cassandra UDT matches your Java class, you can resolve this error effectively.