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

  1. Wrap the nested type in FROZEN: `ALTER TYPE ks.typ ADD field frozen<ks.other_type>;`
  2. If mutability of the nested UDT is required, store it in a separate table instead of nesting
  3. 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

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


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)