apache/cassandra · error · InvalidRequestException

Invalid non-frozen user-defined type

Error message

Invalid non-frozen user-defined type %s for PRIMARY KEY column '%s'

What it means

Thrown from CreateTableStatement's builder while validating PRIMARY KEY columns when a key column's type is a multi-cell (non-frozen) user-defined type — detected via type.isMultiCell() on a type that is not a collection. Primary key columns must be fully frozen so their serialized form is stable, hence the non-frozen UDT is rejected.

Solutions

  1. Freeze the UDT in the column definition: `u frozen<my_udt>`
  2. Flatten the UDT fields into separate clustering/partition columns
  3. Use a tuple type instead of a UDT for the key

Example fix

// before
CREATE TABLE t (u my_udt, PRIMARY KEY (u));
// after
CREATE TABLE t (u frozen<my_udt>, PRIMARY KEY (u));
Defensive patterns

Strategy: validation

Validate before calling

if (keyColumnType.isUDT() && keyColumnType.isMultiCell()) throw new IllegalArgumentException("wrap UDT key column in frozen<>");

Try / catch

try { session.execute(ddl); } catch (InvalidRequestException e) { if (e.getMessage().contains("Invalid non-frozen user-defined type")) { /* rewrite column as frozen<udt> */ } else throw e; }

Prevention

When it happens

Trigger: `CREATE TABLE t (u my_udt, PRIMARY KEY (u))` where my_udt is used without frozen<>.

Common situations: Using a UDT as a composite key for readability; generated schemas placing UDT columns in keys without freezing.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/e07bb126aa79d1ce. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java:308

        HashSet<ColumnIdentifier> primaryKeyColumns = new HashSet<>();
        concat(partitionKeyColumns, clusteringColumns).forEach(column ->
        {
            ColumnProperties properties = columns.get(column);
            if (null == properties)
                throw ire("Unknown column '%s' referenced in PRIMARY KEY for table '%s'", column, tableName);

            if (!primaryKeyColumns.add(column))
                throw ire("Duplicate column '%s' in PRIMARY KEY clause for table '%s'", column, tableName);

            AbstractType<?> type = properties.type;
            if (type.isMultiCell())
            {
                CQL3Type cqlType = properties.cqlType;
                if (type.isCollection())
                    throw ire("Invalid non-frozen collection type %s for PRIMARY KEY column '%s'", cqlType, column);
                else
                    throw ire("Invalid non-frozen user-defined type %s for PRIMARY KEY column '%s'", cqlType, column);
            }

            if (type.isCounter())
                throw ire("counter type is not supported for PRIMARY KEY column '%s'", column);

            if (type.referencesDuration())
                throw ire("duration type is not supported for PRIMARY KEY column '%s'", column);

            if (staticColumns.contains(column))
                throw ire("Static column '%s' cannot be part of the PRIMARY KEY", column);
        });

        List<ColumnProperties> partitionKeyColumnProperties = new ArrayList<>();
        List<ColumnProperties> clusteringColumnProperties = new ArrayList<>();

        partitionKeyColumns.forEach(column ->
        {
            ColumnProperties columnProperties = columns.remove(column);

View on GitHub (pinned to 88fd0f6a0e)