Grails3.3.2+GORM6.1修改UserService重编译时触发空指针异常
I've run into this exact issue before—when working with Grails 3.3.2 and GORM 6.1, modifying an abstract GORM data service class triggers a NPE during auto-recompilation in development mode, crashing the app. Here's a breakdown of the problem, how to reproduce it, and fixes that work:
The Exception
You'll see a stack trace like this during recompilation:
Exception in thread "Thread-8" BUG! exception in phase 'semantic analysis' in source unit '/Users/emmanuj/projects/cleena/demo/src/main/groovy/com/emmanuj/cleena/UserService.groovy' unexpected NullpointerException at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1070) at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:603) at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:581) at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:558) at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:537) at grails.boot.GrailsApp.compileGroovyFile(GrailsApp.groovy:313) at grails.boot.GrailsApp.recompile(GrailsApp.groovy:299) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1427) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182) at grails.boot.GrailsApp$_enableDevelopmentModeWatch_closure1.doCall(GrailsApp.groovy:240) at grails.boot.GrailsApp$_enableDevelopmentModeWatch_closure1.doCall(GrailsApp.groovy) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1427) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:408) at groovy.lang.Closure.run(Closure.java:495) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.NullPointerException at org.grails.datastore.gorm.services.implementers.AbstractDetachedCriteriaServiceImplementor.doImplement(AbstractDetachedCriteriaServiceImplementor.groovy:79) at org.grails.datastore.gorm.services.implementers.AbstractReadOperationImplementer.implement(AbstractReadOperationImplementer.groovy:62) at org.grails.datastore.gorm.services.transform.ServiceTransformation.visitAfterTraitApplied(ServiceTransformation.groovy:278) at org.grails.datastore.gorm.transform.AbstractTraitApplyingGormASTTransformation.visit(AbstractTraitApplyingGormASTTransformation.groovy:52) at org.grails.datastore.gorm.transform.AbstractTraitApplyingGormASTTransformation.visit(AbstractTraitApplyingGormASTTransformation.groovy:42) at org.grails.datastore.gorm.transform.AbstractGormASTTransformation.visit(AbstractGormASTTransformation.groovy:59) at org.codehaus.groovy.transform.ASTTransformationVisitor.visitClass(ASTTransformationVisitor.java:134) at org.codehaus.groovy.transform.ASTTransformationVisitor$2.call(ASTTransformationVisitor.java:178) at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1065) ... 34 more
Reproduction Steps
- Create a new Grails 3.3.2 project
- Add the
Userdomain class:
import grails.compiler.GrailsCompileStatic @GrailsCompileStatic class User { String name static constraints = { } }
- Create a custom data service interface
IUserService:
package com.emmanuj.cleena /** * DataService interface for user domain */ interface IUserService { User get(Serializable id) List<User> list(Map args) Long count() void delete(Serializable id) User save(User user) }
- Create an abstract service class implementing this interface:
import groovy.util.logging.Slf4j import grails.gorm.services.Service @Slf4j @Service(User) abstract class UserService implements IUserService { def grailsApplication List<User> search(User currentUser, long radius, int offset, int max){ return [] } def getZipcodes(String zipCode, long radius) { return [] } }
- Start the app via the Grails interactive console, then modify
UserService.groovy—the recompilation will trigger the NPE.
Root Cause
This is a known bug in GORM 6.1: when using an abstract GORM data service class that implements a custom interface, the GORM AST transformation fails to properly resolve method metadata during auto-recompilation in development mode, leading to the NullPointerException.
Fixes
1. Upgrade GORM (Recommended)
The bug was fixed in GORM 6.1.10 and later. Update your build.gradle to use a newer GORM version:
dependencies { // Keep other dependencies as-is compile "org.grails.plugins:gorm-hibernate5:6.1.10" // Adjust if you're using a different GORM implementation (like mongodb) }
This resolves the issue completely while keeping development auto-recompilation enabled.
2. Disable Auto-Recompilation (Temporary Workaround)
If you can't upgrade right now, turn off auto-recompilation in application.yml:
grails: development: watch: enabled: false
Note: You'll have to manually restart the app after every code change with this approach.
3. Adjust Service Class Structure
Remove the abstract modifier from UserService—GORM's AST transformation will automatically implement the CRUD methods from your IUserService interface without needing an abstract class:
@Slf4j @Service(User) class UserService implements IUserService { def grailsApplication List<User> search(User currentUser, long radius, int offset, int max){ return [] } def getZipcodes(String zipCode, long radius) { return [] } }
This avoids the bug because GORM no longer has to handle abstract class metadata during recompilation.
内容的提问来源于stack exchange,提问作者Emmanuel John

