{"record":{"id":"ae07ce0ee7a1be44","repo":"apache/cassandra","slug":"cannot-add-new-field-s-of-type-s-to-user-type-s","errorCode":null,"errorMessage":"Cannot add new field %s of type %s to user type %s as it would create a circular reference","messagePattern":"Cannot add new field (.+?) of type (.+?) to user type (.+?) as it would create a circular reference","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java","lineNumber":157,"sourceCode":"\n        UserType apply(KeyspaceMetadata keyspace, UserType userType)\n        {\n            if (type.isCounter())\n                throw ire(\"A user type cannot contain counters\");\n\n            if (type.isUDT() && !type.isFrozen())\n                throw ire(\"A user type cannot contain non-frozen UDTs\");\n\n            if (userType.fieldPosition(fieldName) >= 0)\n            {\n                if (!ifFieldNotExists)\n                    throw ire(\"Cannot add field %s to type %s: a field with name %s already exists\", fieldName, userType.getCqlTypeName(), fieldName);\n                return userType;\n            }\n\n            AbstractType<?> fieldType = type.prepare(keyspaceName, keyspace.types).getType();\n            if (fieldType.referencesUserType(userType.name))\n                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());\n\n            Collection<TableMetadata> tablesWithTypeInPartitionKey = findTablesReferencingTypeInPartitionKey(keyspace, userType);\n            if (!tablesWithTypeInPartitionKey.isEmpty())\n            {\n                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\",\n                          fieldName, type, userType.getCqlTypeName(),\n                          String.join(\", \", transform(tablesWithTypeInPartitionKey, TableMetadata::toString)));\n            }\n\n            Guardrails.fieldsPerUDT.guard(userType.size() + 1, userType.getNameAsString(), false, state);\n            type.validate(state, \"Field \" + fieldName);\n\n            List<FieldIdentifier> fieldNames = new ArrayList<>(userType.fieldNames()); fieldNames.add(fieldName);\n            List<AbstractType<?>> fieldTypes = new ArrayList<>(userType.fieldTypes()); fieldTypes.add(fieldType);\n\n            return new UserType(keyspaceName, userType.name, fieldNames, fieldTypes, true);\n        }\n","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java#L139-L175","documentation":"Cassandra rejects an ALTER TYPE ... ADD whose new field's type resolves to a UDT that references the very type being altered, which would make the type definition circular (a cycle the type system cannot serialize or validate). The check is performed in AddField.apply after resolving the field type against the keyspace's types. Circular UDT references are not representable in Cassandra's schema, so the operation fails fast with an InvalidRequest.","triggerScenarios":"Running `ALTER TYPE myks.address ADD owner myks.address` or any ADD whose field type is (directly or nested within a frozen/tuple/collection type) the same UDT being altered, e.g. adding a field of a type that transitively contains the altered type.","commonSituations":"Modeling recursive structures (tree nodes, linked lists) with UDTs; copy-pasting CQL that used another type name; schema migrations where a nested UDT was renamed to the outer type's name.","solutions":["Remove the self/circular reference: declare the new field with a non-recursive type, or model recursion via a separate table instead of a nested UDT","If recursion is needed, store child references by primary key (ids) rather than embedding the UDT in itself","Reorder the cycle: split into two UDTs so neither contains itself, only the other (still disallowed if truly circular — break the cycle entirely)"],"exampleFix":"// before\nALTER TYPE myks.node ADD parent myks.node; // circular\n// after\nALTER TYPE myks.node ADD parent_uuid uuid; // store reference by id","handlingStrategy":"validation","validationCode":"// Before: ALTER TYPE t ADD f t;\nboolean createsCycle = newFieldTypeResolved.referencesUserType(userType.name);\nif (createsCycle) throw new IllegalArgumentException(\"Field type would create a circular UDT reference\");","typeGuard":null,"tryCatchPattern":"try { session.execute(\"ALTER TYPE myks.t ADD f myks.t\"); }\ncatch (InvalidQueryException e) {\n    if (e.getMessage().contains(\"circular reference\")) { /* redesign: break the cycle */ }\n    else throw e;\n}","preventionTips":["Never declare a UDT field whose type is (or contains) the UDT itself","Model recursive data with id references plus a lookup table instead of nested UDTs","Dry-run schema changes in a test keyspace mirroring the types involved"],"tags":["cassandra","cql","udt","schema","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}