apache/cassandra · error · InvalidRequestException

Cannot drop non-frozen column %s of user type %s

Error message

Cannot drop non-frozen column %s of user type %s

What it means

This InvalidRequestException is thrown when dropping a column whose type is a non-frozen (multi-cell) user-defined type (UDT). Non-frozen UDT columns cannot be converted into the equivalent tuple form needed to record the drop in system_schema.dropped_columns, because Cassandra only supports frozen tuples for persisted dropped-column types.

Source

Thrown at src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java:510

        private void dropColumn(KeyspaceMetadata keyspace, TableMetadata table, ColumnIdentifier column, boolean ifExists, TableMetadata.Builder builder)
        {
            ColumnMetadata currentColumn = table.getColumn(column);
            if (null == currentColumn) {
                if (!ifExists)
                    throw ire("Column %s was not found in table '%s'", column, table);
                return;
            }

            if (currentColumn.isPrimaryKeyColumn())
                throw ire("Cannot drop PRIMARY KEY column %s", column);

            /*
             * Cannot allow dropping top-level columns of user defined types that aren't frozen because we cannot convert
             * the type into an equivalent tuple: we only support frozen tuples currently. And as such we cannot persist
             * the correct type in system_schema.dropped_columns.
             */
            if (currentColumn.type.isUDT() && currentColumn.type.isMultiCell())
                throw ire("Cannot drop non-frozen column %s of user type %s", column, currentColumn.type.asCQL3Type());

            if (!table.indexes.isEmpty())
                AlterTableStatement.validateIndexesForColumnModification(table, column, false);

            if (!isEmpty(keyspace.views.forTable(table.id)))
                throw ire("Cannot drop column %s on base table %s with materialized views", currentColumn, table.name);

            builder.removeRegularOrStaticColumn(column);
            builder.recordColumnDrop(currentColumn, getTimestamp());
        }

        /**
         * @return timestamp from query, otherwise return current time in micros
         */
        private long getTimestamp()
        {
            // Prior to Metadata serialization V5, the execution timestamp was not included in AlterSchema
            // serializations. Instead, the current time (from ClientState::getTimestamp) was used, making

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Freeze the UDT column first if the data allows: create a new column of type FROZEN<udt>, copy the data, then drop the old column (note the new column cannot reuse the old name).
  2. Keep the column but stop using it in application code instead of dropping it.
  3. Recreate the table with frozen or non-UDT types and migrate data if the column must not exist.
  4. Check the column type via DESCRIBE TABLE before writing the drop; look for `frozen<...>` in the type.

Example fix

// before
ALTER TABLE users DROP address; -- address is a non-frozen UDT: fails
// after
ALTER TABLE users ADD address_f frozen<address>;
UPDATE users SET address_f = address; -- per row / via migration
ALTER TABLE users DROP address;
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the column type for a non-frozen UDT
ResultSet rs = session.execute(
  "SELECT type FROM system_schema.columns WHERE keyspace_name=? AND table_name=? AND column_name=?",
  ks, table, column);
String type = rs.one().getString("type");
boolean blocked = type.startsWith("<") && !type.startsWith("frozen"); // non-frozen UDT/collection types are encoded with '<'

Try / catch

try {
    session.execute("ALTER TABLE ks.tbl DROP " + column);
} catch (com.datastax.oss.driver.api.core.servererrors.InvalidQueryException e) {
    if (e.getMessage().contains("non-frozen column")) {
        // switch to freeze-copy-drop migration
    } else throw e;
}

Prevention

When it happens

Trigger: Executing `ALTER TABLE ks.tbl DROP udt_col` where udt_col was declared as a plain (non-frozen) UDT, e.g. `address address` rather than `FROZEN<address>`. The check is `currentColumn.type.isUDT() && currentColumn.type.isMultiCell()`.

Common situations: Tables created with unfrozen UDT columns (common in older schemas where non-frozen UDTs were the default for collections of UDTs or top-level UDT columns); migrations that drop legacy UDT columns; developers unaware of the frozen vs non-frozen distinction.

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/272ea8e9a689219b. Report an issue: GitHub.