apache/cassandra · error · InvalidRequestException
Cannot create index on non-frozen UDT column
Error message
Cannot create index on non-frozen UDT column %s
What it means
Cassandra rejects CREATE INDEX when the target column is a user-defined type (UDT) that is not frozen (i.e. isMultiCell). Non-frozen UDTs store each field in separate cells, so a plain index over the whole column cannot be built; only frozen UDT columns (or indexing specific non-frozen UDT sub-fields) are supported.
Solutions
- Declare the column frozen in the table schema: `address frozen<address_udt>`, then create the index.
- Index a specific non-frozen UDT field instead of the whole column: CREATE INDEX ON t(keys(addr)) or ON t(addr.zip) style UDT-field targets if supported.
- Use Storage Attached Index (SAI) via `CREATE CUSTOM INDEX ... USING 'StorageAttachedIndex'` if the field-level indexing needed is supported.
- Re-model the data: split the UDT fields into regular columns that can each be indexed.
Example fix
// before CREATE TABLE t (id int PRIMARY KEY, addr address_udt); CREATE INDEX ON t(addr); // after CREATE TABLE t (id int PRIMARY KEY, addr frozen<address_udt>); CREATE INDEX ON t(addr);
Defensive patterns
Strategy: validation
Validate before calling
if (columnType.startsWith("frozen<")) { /* indexable */ } else if (isUdt(columnType)) throw new IllegalArgumentException("Declare column as frozen<udt> before indexing: " + columnName); Type guard
boolean isIndexableUdt(String colType) { return colType != null && colType.matches("frozen<.*>"); } Try / catch
try { session.execute(createIndexCql); } catch (InvalidQueryException e) { if (e.getMessage().contains("non-frozen UDT")) { /* alter schema to frozen<> or index a UDT field */ } else throw e; } Prevention
- Prefer frozen<udt> for columns you intend to index
- Keep schema DDL and index DDL in the same migration so wrappers are not lost
- Check system_schema.columns type strings before generating index DDL
When it happens
Trigger: CREATE INDEX ... ON table(udt_column) where udt_column is declared as a multi-cell (non-frozen) UDT, e.g. a column typed `address <address_udt>` without the frozen<> wrapper. Raised from validateIndexTarget during CREATE INDEX statement validation.
Common situations: Developers migrate a schema from frozen to non-frozen UDTs to get partial updates, then forget existing secondary indexes are no longer valid; or they model a UDT column and try to index it directly like a scalar column.
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
- Altering field types is no longer supported
- Cannot add new field
- Cannot add new field
- Cannot alter user type
- Cannot drop user type
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/57c8f6a6576db2af.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java:315
// Frozen collections are only supported with SAI indexes.
if (isNonSAIIndex && baseType.isCollection() && !column.type.isMultiCell())
{
if (target.type == Type.VALUES || target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES)
{
throw ire(CREATE_ON_FROZEN_COLUMN, target.type.toString(), column.name, column.name);
}
}
if (!(baseType instanceof MapType) && (target.type == Type.KEYS || target.type == Type.KEYS_AND_VALUES ))
throw ire(CREATE_WITH_NON_MAP_TYPE, target.type, column);
// Can't query map[key]=value on clustering key columns, so ENTRIES index would be not queryable.
if (column.isClusteringColumn() && baseType instanceof MapType && !column.type.isMultiCell()
&& target.type == Type.KEYS_AND_VALUES)
throw ire(ENTRIES_INDEX_ON_FROZEN_MAP_CLUSTERING_KEY_NOT_SUPPORTED, column.name);
if (column.type.isUDT() && column.type.isMultiCell())
throw ire(CREATE_ON_NON_FROZEN_UDT, column);
}
/**
* Checks if the given index attributes represent a Storage Attached Index.
*/
private boolean isSAIIndex(IndexAttributes attrs)
{
return attrs.isCustom && IndexMetadata.isSAIIndex(attrs.customClass);
}
private String generateIndexName(KeyspaceMetadata keyspace, List<IndexTarget> targets)
{
String baseName = targets.size() == 1
? IndexMetadata.generateDefaultIndexName(tableName, targets.get(0).column)
: IndexMetadata.generateDefaultIndexName(tableName);
return keyspace.findAvailableIndexName(baseName);
}
View on GitHub (pinned to 88fd0f6a0e)