{"record":{"id":"28a35514877eb7fe","repo":"NationalSecurityAgency/ghidra","slug":"dbannotatedobject-objecttype-must-have-dbannota","errorCode":null,"errorMessage":"DBAnnotatedObject {objectType} must have @DBAnnotatedObjectInfo annotation","messagePattern":"DBAnnotatedObject (.+?) must have @DBAnnotatedObjectInfo annotation","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java","lineNumber":1341,"sourceCode":"\t/**\n\t * Get the codecs for the given class\n\t * \n\t * @param <OT> the type of objects to store in the table\n\t * @param objectType the class describing {@link OT}\n\t * @return the codecs, in column order\n\t */\n\tstatic <OT extends DBAnnotatedObject> List<DBFieldCodec<?, OT, ?>> getCodecs(\n\t\t\tClass<OT> objectType) {\n\t\treturn getInfo(objectType).codecs;\n\t}\n\n\t/**\n\t * The non-cached implementation of {@link #getInfo(Class)}\n\t */\n\tprivate static <OT extends DBAnnotatedObject> TableInfo<OT> buildInfo(Class<OT> objectType) {\n\t\tDBAnnotatedObjectInfo info = objectType.getAnnotation(DBAnnotatedObjectInfo.class);\n\t\tif (info == null) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\tDBAnnotatedObject.class.getSimpleName() + \" \" + objectType.getName() +\n\t\t\t\t\t\" must have @\" + DBAnnotatedObjectInfo.class.getSimpleName() + \" annotation\");\n\t\t}\n\n\t\tMap<String, Field> fields = new LinkedHashMap<>();\n\t\tList<Field> indexFields = new ArrayList<>();\n\t\tList<Field> sparseFields = new ArrayList<>();\n\t\tcollectFields(objectType, fields, indexFields, sparseFields);\n\n\t\tTableInfo<OT> tableInfo =\n\t\t\tnew TableInfo<>(objectType, info.version(), fields, indexFields, sparseFields);\n\t\ttableInfo.writeColumnNumbers(objectType);\n\n\t\treturn tableInfo;\n\t}\n\n\t/**\n\t * Collect the fields of the given class, recursively, starting with its super class","sourceCodeStart":1323,"sourceCodeEnd":1359,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java#L1323-L1359","documentation":"buildInfo requires every concrete DBAnnotatedObject subclass to carry @DBAnnotatedObjectInfo(version=N) so the factory knows the schema version and column layout. The annotation is read via objectType.getAnnotation(...) (not inherited), so a missing annotation on the concrete class leaves no safe default and the table cannot be opened or migrated - IllegalArgumentException is thrown.","triggerScenarios":"Opening/creating a store for a class extending DBAnnotatedObject whose concrete class lacks @DBAnnotatedObjectInfo. Putting the annotation only on an abstract superclass does NOT satisfy getAnnotation, so subclasses still throw.","commonSituations":"Adding a new DBAnnotatedObject subclass and forgetting the class annotation; annotating an abstract base only; introducing a subclass layer during refactor without re-annotating.","solutions":["Add @DBAnnotatedObjectInfo(version=N) directly onto the concrete subclass you open as a store.","Confirm the version number matches the existing database schema (or intentionally bump it with migration logic).","If you share config across subclasses, still annotate each concrete class since the lookup is non-inherited."],"exampleFix":"// before\nclass MyObj extends DBAnnotatedObject {\n    MyObj(DBCachedObjectStore<?> store, DBRecord record) { super(store, record); }\n}\n// after\n@DBAnnotatedObjectInfo(version = 1)\nclass MyObj extends DBAnnotatedObject {\n    MyObj(DBCachedObjectStore<?> store, DBRecord record) { super(store, record); }\n}","handlingStrategy":"validation","validationCode":"if (objectType.getAnnotation(DBAnnotatedObjectInfo.class) == None) {\n    throw new IllegalStateException(objectType.getName() + \" missing @DBAnnotatedObjectInfo\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always annotate concrete DBAnnotatedObject subclasses with @DBAnnotatedObjectInfo.","Remember the lookup is non-inherited - an annotation on an abstract base is not enough."],"tags":["database","annotation","schema","configuration"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}