{"id":"d80edc35df7df201","repo":"junit-team/junit5","slug":"a-namespacedhierarchicalstore-cannot-be-modified-o","errorCode":null,"errorMessage":"A NamespacedHierarchicalStore cannot be modified or queried after it has been closed","messagePattern":"A NamespacedHierarchicalStore cannot be modified or queried after it has been closed","errorType":"exception","errorClass":"NamespacedHierarchicalStoreException","httpStatus":null,"severity":"error","filePath":"junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java","lineNumber":500,"sourceCode":"\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate <T, V> T castNonNullToRequiredType(Object key, V value, Class<T> requiredType) {\n\t\tif (isAssignableTo(value, requiredType)) {\n\t\t\tif (requiredType.isPrimitive()) {\n\t\t\t\treturn (T) requireNonNull(getWrapperType(requiredType)).cast(value);\n\t\t\t}\n\t\t\treturn requiredType.cast(value);\n\t\t}\n\t\t// else\n\t\tthrow new NamespacedHierarchicalStoreException(\n\t\t\t\"Object stored under key [%s] is not of required type [%s], but was [%s]: %s\".formatted(key,\n\t\t\t\trequiredType.getName(), value.getClass().getName(), value));\n\t}\n\n\tprivate void rejectIfClosed() {\n\t\tif (this.closed) {\n\t\t\tthrow new NamespacedHierarchicalStoreException(\n\t\t\t\t\"A NamespacedHierarchicalStore cannot be modified or queried after it has been closed\");\n\t\t}\n\t}\n\n\tprivate record CompositeKey<N>(N namespace, Object key) {\n\n\t\tCompositeKey {\n\t\t\tPreconditions.notNull(namespace, \"namespace must not be null\");\n\t\t\tPreconditions.notNull(key, \"key must not be null\");\n\t\t}\n\n\t}\n\n\tprivate interface StoredValue {\n\n\t\tint order();\n\n\t\t@Nullable","sourceCodeStart":482,"sourceCodeEnd":518,"githubUrl":"https://github.com/junit-team/junit5/blob/956246301e03d757539f7b76dd5a91f298637563/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java#L482-L518","documentation":"Thrown by NamespacedHierarchicalStore.rejectIfClosed() (line 498-503) as a NamespacedHierarchicalStoreException whenever any mutating or querying method is called after close() has been invoked. The store's closed flag is set in the finally of close(); after that, get/put/remove/computeIfAbsent/getOrComputeIfAbsent all reject. Closing is irreversible (close() is idempotent but cannot reopen).","triggerScenarios":"An extension or engine code path holds a reference to an ExtensionContext.Store (backed by NamespacedHierarchicalStore) and tries to read/write it after the corresponding test/extension scope has finished (e.g. a background thread outliving the test, or an after-callback reusing a captured store). Parent/child store closing does not close each other, but the store for a finished test is closed.","commonSituations":"Async work started in a test method that touches the Store after @AfterEach; extension @AfterEach callbacks touching a store whose engine-level parent closed; caching a Store in a static field and reusing across tests; thread pools not joined before test completion.","solutions":["Ensure all work that touches the Store completes before the extension/test method returns (join threads, cancel scheduled tasks).","Do not retain Store/ExtensionContext references beyond their documented scope.","If you need cross-test state, use a dedicated namespace in an engine-level store or an external mechanism, not a per-test store.","Check store.isClosed() defensively before access in long-lived/background code paths."],"exampleFix":"// before\nprivate ExtensionContext.Store cached;\n@BeforeEach void cache(ExtensionContext ctx) { this.cached = ctx.getStore(...); }\n@AfterAll void useStoreAfterTests() { cached.get(\"x\"); } // store for a prior test already closed -> throws\n\n// after\n@AfterEach void useStoreInScope(ExtensionContext ctx) {\n    ExtensionContext.Store store = ctx.getStore(...); // fresh, in-scope\n    store.get(\"x\");\n}","handlingStrategy":"validation","validationCode":"if (store.isClosed()) {\n    throw new IllegalStateException(\"store already closed; do not access out of scope\");\n}\nObject value = store.get(MY_NS, key);","typeGuard":"static boolean isStoreUsable(ExtensionContext.Store store) {\n    // ExtensionContext.Store does not expose isClosed; treat any post-test access as unsafe.\n    // Only access within the callback that received the Store.\n    return true; // scope discipline is the real guard\n}","tryCatchPattern":"try {\n    return store.get(MY_NS, key);\n} catch (NamespacedHierarchicalStoreException e) {\n    if (e.getMessage().contains(\"closed\")) {\n        // background work outlived the test; reschedule or skip\n        log.debug(\"store closed; skipping stale access\", e);\n        return null;\n    }\n    throw e;\n}","preventionTips":["Never retain Store/ExtensionContext references beyond their callback scope.","Join/cancel all background tasks before the test or extension method returns.","Use engine-level namespaces for cross-test state, not per-test stores.","Check isClosed() defensively before any access from long-lived code."],"tags":["junit-platform","store","extension-context","lifecycle","use-after-close"],"analyzedSha":"956246301e03d757539f7b76dd5a91f298637563","analyzedAt":"2026-08-04T19:25:17.049Z","schemaVersion":2}