{"record":{"id":"e27b81df736d8bb5","repo":"quarkusio/quarkus","slug":"methods-that-are-annotated-with-jpa-listener-annot","errorCode":null,"errorMessage":"Methods that are annotated with JPA Listener annotations should not be private. Offending method is '${declaringClass}#${method}'","messagePattern":"Methods that are annotated with JPA Listener annotations should not be private\\. Offending method is '(.+?)#(.+?)'","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java","lineNumber":325,"sourceCode":"                        MethodInfo method = target.asMethod();\n                        if (Modifier.isPrivate(method.flags())) {\n                            if (beanScope.getDotName().equals(BuiltinScope.SINGLETON.getName())) {\n                                // we can safely transform in this case\n                                producer.produce(new BytecodeTransformerBuildItem(method.declaringClass().name().toString(),\n                                        new BiFunction<>() {\n                                            @Override\n                                            public ClassVisitor apply(String cls, ClassVisitor clsVisitor) {\n                                                var classTransformer = new ClassTransformer(cls);\n                                                classTransformer.modifyMethod(MethodDescriptor.of(method))\n                                                        .removeModifiers(Modifier.PRIVATE);\n                                                return classTransformer.applyTo(clsVisitor);\n                                            }\n                                        }));\n                            } else {\n                                // we can't transform because the client proxy does not know about the transformation and\n                                // will therefore simply copy the private method which will then likely fail because it does\n                                // not contain the injected fields\n                                throw new IllegalArgumentException(\n                                        \"Methods that are annotated with JPA Listener annotations should not be private. Offending method is '\"\n                                                + method.declaringClass().name() + \"#\" + method.name() + \"'\");\n                            }\n                        }\n                    }\n                }\n            } else {\n                // we don't really know what to do here, just bail and CDI will figure it out\n            }\n        }\n    }\n\n    @BuildStep\n    void registerAnnotations(BuildProducer<AdditionalBeanBuildItem> additionalBeans,\n            BuildProducer<BeanDefiningAnnotationBuildItem> beanDefiningAnnotations) {\n        // add the @PersistenceUnit, @PersistenceUnitExtension, @JsonFormat and @XmlFormat classes\n        // otherwise they won't be registered as qualifiers\n        additionalBeans.produce(AdditionalBeanBuildItem.builder()","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java#L307-L343","documentation":"Hibernate ORM's CDI processor transforms JPA listener callback methods (e.g. @PrePersist, @PostLoad annotated methods on entity listeners) to work with Quarkus bean injection. A private callback method cannot be safely transformed because the client proxy would copy the method without the injected fields, so the build fails fast with an IllegalArgumentException naming the offending class#method.","triggerScenarios":"A build-step in transformBeans encounters an entity listener or entity method annotated with a JPA listener annotation (@PrePersist, @PostUpdate, @PreRemove, @PostPersist, @PostUpdate, @PostLoad) that is declared private and whose declaring class cannot be transformed (the client proxy would not know about the transformation).","commonSituations":"Defining lifecycle callback methods as private inside @Entity or @EntityListener classes; migrating a legacy Hibernate app where callbacks were made private for encapsulation.","solutions":["Change the offending method's visibility from private to at least package-private (recommended by the JPA spec) or public","Remove bean injection from the listener so no transformation is needed, keeping in mind private listeners are still non-standard","Refactor the callback logic into a CDI event observer instead of a JPA listener"],"exampleFix":"// before\n@Entity\npublic class User {\n    @PrePersist\n    private void onCreate() { ... }\n}\n// after\n@Entity\npublic class User {\n    @PrePersist\n    void onCreate() { ... }\n}","handlingStrategy":"validation","validationCode":"// In your entity/listener classes, audit callbacks before build:\nfor (java.lang.reflect.Method m : MyListener.class.getDeclaredMethods()) {\n    if (java.lang.reflect.Modifier.isPrivate(m.getModifiers()) &&\n        java.util.Arrays.stream(m.getAnnotations()).anyMatch(a ->\n            a.annotationType().getName().startsWith(\"jakarta.persistence.\"))) {\n        throw new IllegalStateException(\"JPA callback must not be private: \" + m);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never declare @PrePersist/@PostLoad/etc. methods private; use package-private or public","Keep entity listeners free of field injection unless you know Quarkus can transform them","Code-review entity lifecycle callbacks during legacy migrations"],"tags":["jpa","hibernate-orm","entity-listener","build-time"],"backgroundTag":"private-jpa-callback-method","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}