apache/cassandra · error · InvalidRequestException
A user type cannot contain non-frozen UDTs
Error message
A user type cannot contain non-frozen UDTs
What it means
Thrown when a field being added to or changed in a user-defined type is itself a non-frozen UDT. Cassandra requires nested UDTs inside other UDTs to be frozen, since non-frozen UDTs cannot be used in that nested position. The check runs on the prepared field type before applying the alteration.
Solutions
- Wrap the nested type in FROZEN: `ALTER TYPE ks.typ ADD field frozen<ks.other_type>;`
- If mutability of the nested UDT is required, store it in a separate table instead of nesting
- Freeze an entire tuple-like structure if multiple nested fields are needed
Example fix
// before ALTER TYPE ks.person ADD address ks.address; // after ALTER TYPE ks.person ADD address frozen<ks.address>;
Defensive patterns
Strategy: validation
Validate before calling
String t = fieldType.trim().toLowerCase();
if (!t.startsWith("frozen<") && isKnownUserType(keyspace, t))
throw new IllegalArgumentException("Nested UDT " + t + " must be frozen"); Type guard
boolean isNonFrozenUdt(String cqlFieldType) {
String t = cqlFieldType.trim().toLowerCase();
return !t.startsWith("frozen<") && t.matches("[a-z0-9_]+[.<].*");
} Try / catch
try { session.execute(alterTypeStmt); }
catch (InvalidQueryException e) {
if ("A user type cannot contain non-frozen UDTs".equals(e.getMessage())) {
// retry with frozen<...> wrapper
} else throw e;
} Prevention
- Always wrap nested UDTs in frozen<...> in ALTER/CREATE TYPE statements
- Add a lint step to schema migrations that flags non-frozen UDT references
- Remember tables are the only place non-frozen UDT collections are allowed
When it happens
Trigger: Executing `ALTER TYPE ks.typ ADD field ks.other_type` where `ks.other_type` is a non-frozen user type (declared without the FROZEN keyword), or altering a field to such a type.
Common situations: Forgetting FROZEN when referencing another UDT; migrations copied from table definitions where non-frozen UDTs are allowed in some positions; older schemas created before frozen enforcement.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Argument ' ' cannot be frozen; remove frozen<> modifier from
- Invalid operation ( ) for frozen UDT column
- Non-frozen UDTs are not allowed inside collections:
- Non-frozen UDTs with nested non-frozen collections are not…
- A user type cannot contain counters
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/b25959550c4a2227.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java:146
super.validate(state);
// save the query state to use it for guardrails validation in #apply
this.state = state;
}
@Override
public boolean compatibleWith(ClusterMetadata metadata)
{
return metadata.directory.commonSerializationVersion.isAtLeast(Version.V0);
}
UserType apply(KeyspaceMetadata keyspace, UserType userType)
{
if (type.isCounter())
throw ire("A user type cannot contain counters");
if (type.isUDT() && !type.isFrozen())
throw ire("A user type cannot contain non-frozen UDTs");
if (userType.fieldPosition(fieldName) >= 0)
{
if (!ifFieldNotExists)
throw ire("Cannot add field %s to type %s: a field with name %s already exists", fieldName, userType.getCqlTypeName(), fieldName);
return userType;
}
AbstractType<?> fieldType = type.prepare(keyspaceName, keyspace.types).getType();
if (fieldType.referencesUserType(userType.name))
throw ire("Cannot add new field %s of type %s to user type %s as it would create a circular reference", fieldName, type, userType.getCqlTypeName());
Collection<TableMetadata> tablesWithTypeInPartitionKey = findTablesReferencingTypeInPartitionKey(keyspace, userType);
if (!tablesWithTypeInPartitionKey.isEmpty())
{
throw ire("Cannot add new field %s of type %s to user type %s as the type is being used in partition key by the following tables: %s",
fieldName, type, userType.getCqlTypeName(),
String.join(", ", transform(tablesWithTypeInPartitionKey, TableMetadata::toString)));View on GitHub (pinned to 88fd0f6a0e)