{"record":{"id":"5c0f634443ed9055","repo":"apache/cassandra","slug":"cannot-add-a-column-s-of-type-s-incompatible","errorCode":null,"errorMessage":"Cannot add a column '%s' of type %s, incompatible with previously dropped column '%s' of type %s","messagePattern":"Cannot add a column '(.+?)' of type (.+?), incompatible with previously dropped column '(.+?)' of type (.+?)","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java","lineNumber":386,"sourceCode":"\n            // check for nested non-frozen UDTs or collections in a non-frozen UDT\n            if (type.isUDT() && type.isMultiCell())\n            {\n                for (AbstractType<?> fieldType : ((UserType) type).fieldTypes())\n                {\n                    if (fieldType.isMultiCell())\n                        throw ire(\"Non-frozen UDTs with nested non-frozen collections are not supported for column \" + column.name);\n                }\n            }\n\n            ColumnMetadata droppedColumn = table.getDroppedColumn(name.bytes);\n            if (null != droppedColumn)\n            {\n                // After #8099, not safe to re-add columns of incompatible types - until *maybe* deser logic with dropped\n                // columns is pushed deeper down the line. The latter would still be problematic in cases of schema races.\n                if (!type.isSerializationCompatibleWith(droppedColumn.type))\n                {\n                    throw ire(\"Cannot add a column '%s' of type %s, incompatible with previously dropped column '%s' of type %s\",\n                              name,\n                              type.asCQL3Type(),\n                              name,\n                              droppedColumn.type.asCQL3Type());\n                }\n\n                if (droppedColumn.isStatic() != isStatic)\n                {\n                    throw ire(\"Cannot re-add previously dropped column '%s' of kind %s, incompatible with previous kind %s\",\n                              name,\n                              isStatic ? ColumnMetadata.Kind.STATIC : ColumnMetadata.Kind.REGULAR,\n                              droppedColumn.kind);\n                }\n\n                // Cannot re-add a dropped counter column. See #7831.\n                if (table.isCounter())\n                    throw ire(\"Cannot re-add previously dropped counter column %s\", name);\n            }","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java#L368-L404","documentation":"After a column is dropped, Cassandra remembers its type so it can deserialize any lingering data. Re-adding a column of the same name with a type that is not serialization-compatible with the dropped type is rejected (CASSANDRA-8099), because reading old sstables could produce corrupted or misinterpreted values, especially under schema races.","triggerScenarios":"ALTER TABLE t DROP col; then ALTER TABLE t ADD col <different_type> where the new type fails AbstractType.isSerializationCompatibleWith(dropped type) — e.g. int dropped, text re-added; or same name with an incompatible non-frozen/frozen collection change.","commonSituations":"Reusing a dropped column name with a different type; schema-sync tools renaming/re-typing a column by drop+add; migrating after a type change decision; accidental reuse of a name from long ago that is still in the dropped-columns history.","solutions":["Use a new column name instead of reusing the dropped one, e.g. ALTER TABLE t ADD col2 <type>.","Re-add the column with exactly the same type (or a serialization-compatible one) as when it was dropped.","Check the dropped column's recorded type via system_schema.columns / sstable metadata (system.dropped_columns) to match it.","If the old data is fully expired (GC grace passed) and the table history cleared, recreate the table to purge the dropped-column record, then add the column with the new type."],"exampleFix":"// before\nALTER TABLE t DROP cnt;\nALTER TABLE t ADD cnt text;   -- fails: was int\n// after\nALTER TABLE t DROP cnt;\nALTER TABLE t ADD cnt_text text;  -- new name\n-- or: ALTER TABLE t ADD cnt int;  -- same, compatible type","handlingStrategy":"validation","validationCode":"// before re-adding a dropped column name\nColumnMetadata dropped = table.getDroppedColumn(ByteBufferUtil.bytes(colName));\nif (dropped != null && !newType.isSerializationCompatibleWith(dropped.type))\n    throw new IllegalArgumentException(\"Type \" + newType.asCQL3Type() + \" incompatible with dropped column of \" + dropped.type.asCQL3Type());","typeGuard":"boolean canReAdd(TableMetadata t, String name, AbstractType<?> type) {\n    ColumnMetadata d = t.getDroppedColumn(UTF8Type.instance.decompose(name));\n    return d == null || type.isSerializationCompatibleWith(d.type);\n}","tryCatchPattern":"try {\n    session.execute(alterAdd);\n} catch (InvalidQueryException e) {\n    if (e.getMessage().contains(\"incompatible with previously dropped column\"))\n        throw new SchemaConflictException(\"Choose a new column name or the original type\", e);\n    throw e;\n}","preventionTips":["Never reuse dropped column names for different types; prefer new names (col_v2).","Query system.dropped_columns / system_schema history to check a name's dropped type before re-adding.","Treat DROP COLUMN as permanent from the application's naming perspective.","Keep schema migrations idempotent by generating unique column names per type change."],"tags":["cql","schema","alter-table","type-compatibility","dropped-column"],"backgroundTag":"type-mismatch","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}