{"record":{"id":"1a488c0694576b29","repo":"hibernate/hibernate-orm","slug":"unable-to-perform-aftertransactioncompletion-callb","errorCode":null,"errorMessage":"Unable to perform afterTransactionCompletion callback: {}","messagePattern":"Unable to perform afterTransactionCompletion callback: (.+?)","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java","lineNumber":80,"sourceCode":"\t\t\t\t\titerator.remove();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn hasPendingBulkOperationCleanUpActions;\n\t}\n\n\tprivate boolean callAfterCompletion(boolean success, AfterCompletionCallback process) {\n\t\ttry {\n\t\t\tprocess.doAfterTransactionCompletion( success, session );\n\t\t\treturn true;\n\t\t}\n\t\tcatch (CacheException ce) {\n\t\t\tCORE_LOGGER.unableToReleaseCacheLock( ce );\n\t\t\t// continue loop\n\t\t\treturn false;\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow new HibernateException(\n\t\t\t\t\t\"Unable to perform afterTransactionCompletion callback: \" + e.getMessage(), e );\n\t\t}\n\t}\n\n\tprivate void invalidateCaches() {\n\t\tfinal var factory = session.getFactory();\n\t\tif ( factory.getSessionFactoryOptions().isQueryCacheEnabled() ) {\n\t\t\tfactory.getCache().getTimestampsCache().\n\t\t\t\t\tinvalidate( querySpacesToInvalidate.toArray( EMPTY_STRING_ARRAY ), session );\n\t\t}\n\t\tquerySpacesToInvalidate.clear();\n\t}\n}\n","sourceCodeStart":62,"sourceCodeEnd":94,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/engine/internal/AfterTransactionCompletionProcessQueue.java#L62-L94","documentation":"After a transaction completes, Hibernate runs registered AfterCompletionCallback instances (cache evictions, post-commit hooks from interceptors/listeners). callAfterCompletion lets CacheException escape quietly (logged, loop continues) but wraps any other exception in HibernateException with this message. Note the timing: the transaction has already committed or rolled back, so this exception surfaces from the completion machinery (often the JTA/resource synchronizer), not from commit() itself.","triggerScenarios":"A registered afterCompletion callback throwing - custom Interceptor.afterTransactionCompletion, event listeners, Envers/audit hooks, or second-level cache operations that fail with something other than CacheException (e.g. NPE in user code, failed notification of a clustered cache). Thrown while the session processes completion, aborting the remaining queued callbacks.","commonSituations":"Custom interceptors doing post-commit notifications (message publishing, file writes) that throw; cache providers (Infinispan/Ehcache) with connectivity problems surfacing as non-CacheException; errors thrown from afterCompletion in tests with TransactionUtil; Envers listeners misbehaving after commit.","solutions":["Inspect the nested cause - it identifies which callback failed; fix that callback's own error handling.","Make custom afterTransactionCompletion implementations defensive: catch their own exceptions and log instead of propagating.","If the cause is cache-related, check the second-level cache provider's health/configuration (CacheException is tolerated, other provider runtime exceptions are not).","Keep after-commit side effects out of interceptors - use Spring @TransactionalEventListener(AFTER_COMMIT) or explicit post-commit queues that retry."],"exampleFix":"// before - interceptor callback can throw and abort completion processing\npublic class NotifyInterceptor implements Interceptor {\n    @Override\n    public void afterTransactionCompletion(Transaction tx) {\n        messageBus.publish(buildNotification()); // throws -> HibernateException\n    }\n}\n// after - swallow and log inside user callback\n    @Override\n    public void afterTransactionCompletion(Transaction tx) {\n        try { messageBus.publish(buildNotification()); }\n        catch (Exception e) { log.error(\"post-commit notify failed\", e); }\n    }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Completion failures surface after commit; isolate them at the synchronization boundary\ntry {\n    transaction.commit(); // or let JTA drive completion\n} finally {\n    // callbacks already ran; if a HibernateException 'Unable to perform afterTransactionCompletion callback'\n    // escaped, the data IS committed - log and queue a repair instead of retrying the business operation\n}","preventionTips":["Make Interceptor.afterTransactionCompletion implementations catch their own exceptions.","Move external side effects (messaging, emails) to a durable post-commit queue, not the callback itself.","Monitor cache provider health: non-CacheException cache errors during completion abort remaining callbacks."],"tags":["transaction","callbacks","interceptor","post-commit","second-level-cache"],"backgroundTag":"transaction-completion-callback-failure","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}