{"record":{"id":"dc42501ccd9b3a51","repo":"halo-dev/halo","slug":"duplicate-key-s-for-extension-s","errorCode":null,"errorMessage":"Duplicate key '%s' for extension '%s'","messagePattern":"Duplicate key '(.+?)' for extension '(.+?)'","errorType":"validation","errorClass":"DuplicateKeyException","httpStatus":null,"severity":"error","filePath":"application/src/main/java/run/halo/app/extension/index/MultiValueIndex.java","lineNumber":245,"sourceCode":"                return;\n            }\n            invertedIndex.put(primaryKey, newKeys);\n            // remove previous keys\n            if (!CollectionUtils.isEmpty(previousKeys)) {\n                previousKeys.forEach(key -> index.computeIfPresent(key, (k, v) -> {\n                    v.remove(primaryKey);\n                    return v.isEmpty() ? null : v;\n                }));\n            }\n            // add new keys\n            if (!CollectionUtils.isEmpty(newKeys)) {\n                for (K key : newKeys) {\n                    index.compute(key, (k, v) -> {\n                        if (v == null) {\n                            v = ConcurrentHashMap.newKeySet();\n                        }\n                        if (spec.isUnique() && !v.isEmpty()) {\n                            throw new DuplicateKeyException(\n                                    String.format(\"Duplicate key '%s' for extension '%s'\", k, primaryKey));\n                        }\n                        v.add(primaryKey);\n                        return v;\n                    });\n                }\n                nullKeyValues.remove(primaryKey);\n            } else {\n                nullKeyValues.add(primaryKey);\n            }\n        }\n\n        @Override\n        public void rollback() {\n            if (Objects.equals(this.previousKeys, newKeys) || !committed) {\n                return;\n            }\n            // remove possibly added new keys","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/extension/index/MultiValueIndex.java#L227-L263","documentation":"Thrown as org.springframework.dao.DuplicateKeyException during the commit phase of a multi-value index upsert (MultiValueIndex.UpsertTransactionalOperation.commit). It fires only when the index spec is marked unique and a key in newKeys already maps to one or more different primary keys, so accepting it would break the uniqueness invariant. Halo's extension store uses this to enforce unique constraints on extension fields (e.g. a unique name or slug).","triggerScenarios":"Creating or updating an extension whose indexed field is declared unique, where the supplied value collides with a value already stored under a different extension name/primary key. Concretely: a second extension resource is created/updated so that its computed index key set overlaps an existing key while spec.isUnique()==true and the existing entry set for that key is non-empty.","commonSituations":"Duplicate display names or slugs on resources that carry a unique index; two users/plugins racing to register the same identifier; a migration or import that re-inserts rows whose unique field already exists; renaming a resource to a name another resource already holds.","solutions":["Look up the existing extension by the colliding key value before create/update and reuse or skip it instead of inserting a duplicate.","Pick a different, unique value for the indexed field and retry the create/update.","If duplicates are legitimately expected, change the index spec so isUnique() is false (schema/extension definition change, not a runtime fix).","Delete the conflicting older resource first, then re-attempt the write."],"exampleFix":"// before\nclient.create(dupeNamedResource).block();\n\n// after\nvar existing = indexer.all().stream()\n    .filter(id -> \"the-colliding-value\".equals(readField(id))).findAny();\nif (existing.isPresent()) {\n    return; // or update existing instead\n}\nclient.create(dupeNamedResource).block();","handlingStrategy":"validation","validationCode":"// Before create/update, query the unique index for the candidate key\nSet<String> owners = valueIndexQuery.equal(\"<indexedField>\", candidateKey);\nboolean willCollide = owners.stream().anyMatch(id -> !id.equals(currentPrimaryKey));\nif (willCollide) {\n    return Mono.error(new IllegalStateException(\"Value already used: \" + candidateKey));\n}","typeGuard":"// Java has no structural type guard; validate via index key type\nstatic <K> boolean isUniqueSafe(ValueIndexQuery<K> idx, K candidate, String selfId) {\n    return idx.equal(candidate).stream().noneMatch(id -> !id.equals(selfId));\n}","tryCatchPattern":"try {\n    extensionClient.create(resource).block();\n} catch (DuplicateKeyException e) {\n    // surface a user-friendly 'duplicate value' message; offer the existing resource\n    throw new ConflictException(\"A resource with this value already exists\", e);\n}","preventionTips":["Pre-check the unique index before every create/update that touches the indexed field.","Keep index uniqueness aligned with the schema; never relax it at runtime.","Serialize concurrent writes that target the same unique value."],"tags":["index","unique-constraint","extension-store","data-integrity"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}