Nope, the parent is a plain abstract class as per doc. This is the first time we're using inheritance in a project, since I got issues the first time I tryed it in 2016. We are keeping it minimal, it's just a common class to handle auditing fields.
Anyway, calling makeDirty() before saving fixes the issue, but if this is not intended I will open an issue for Grails Data Hibernate 7 Gianluca Sartori -- https://dueuno.com On Tue, 25 Aug 2026 at 18:15, James Daugherty via dev <[email protected]> wrote: > Do you have `@DirtyCheck` on the parent? I think that's what causes > this behavior. > > On Tue, Aug 25, 2026 at 6:49 AM Gianluca Sartori <[email protected]> > wrote: > > > > I am not sure if this is a bug or the intended behaviour but I am quite > > sure the documentation does not talk about it. @Walter can you please > give > > it a look? > > > > I've used AI to summarize the findings. > > > > > > *GORM/Hibernate does not detect changes to inherited properties during > > dirty checking.* > > > > The property is correctly persisted if another property of the entity is > > modified at the same time, or if markDirty() is explicitly called. > However, > > changing only the inherited property does not result in an UPDATE being > > issued. > > Expected behavior > > > > Changing a persistent property should mark the entity as dirty and cause > > the property to be persisted when calling: > > > > obj.save(flush: true) > > > > Actual behavior > > > > The entity is not considered dirty when only an inherited property is > > changed. > > > > For example: > > > > TPaymentMethod obj = TPaymentMethod.get(id) > > > > obj.dateDisabled = LocalDateTime.now() > > obj.usernameDisabled = 'TEST' > > > > println obj.isDirty() > > println obj.isDirty('dateDisabled') > > println obj.isDirty('usernameDisabled') > > > > obj.save(flush: true, failOnError: true) > > > > The output is: > > > > dirty=false > > dirty date=false > > dirty username=false > > > > No SQL UPDATE is generated and the values are not persisted. > > > > However, if another property declared directly on TPaymentMethod is > changed: > > > > obj.description = obj.description + ' TEST' > > obj.dateDisabled = LocalDateTime.now() > > > > obj.save(flush: true, failOnError: true) > > > > the entity is detected as dirty and both changes are persisted correctly. > > > > Explicitly marking the entity as dirty also works: > > > > obj.dateDisabled = LocalDateTime.now() > > obj.usernameDisabled = 'TEST' > > > > obj.markDirty() > > > > obj.save(flush: true, failOnError: true) > > > > Minimal exampleBase class > > > > abstract class SystemManaged { > > > > Long id > > Long version > > > > LocalDateTime dateCreated > > LocalDateTime lastUpdated > > LocalDateTime dateDeleted > > LocalDateTime dateDisabled > > > > String usernameCreated > > String usernameUpdated > > String usernameDeleted > > String usernameDisabled > > > > static constraints = { > > id nullable: true > > version nullable: true > > > > dateCreated nullable: true > > lastUpdated nullable: true > > dateDeleted nullable: true > > dateDisabled nullable: true > > > > usernameCreated nullable: true > > usernameUpdated nullable: true > > usernameDeleted nullable: true > > usernameDisabled nullable: true > > } > > } > > > > Domain class > > > > @GrailsCompileStatic > > class TPaymentMethod extends SystemManaged > > implements GormEntity, MultiTenant<TPaymentMethod> { > > > > String description > > > > static constraints = { > > description nullable: false > > } > > } > > > > Reproduction > > > > @Transactional > > void test(Long id) { > > TPaymentMethod obj = TPaymentMethod.get(id) > > > > obj.dateDisabled = LocalDateTime.now() > > obj.usernameDisabled = 'TEST' > > > > println "dirty=${obj.isDirty()}" > > println "dirty dateDisabled=${obj.isDirty('dateDisabled')}" > > println "dirty usernameDisabled=${obj.isDirty('usernameDisabled')}" > > println "dirty properties=${obj.dirtyPropertyNames}" > > > > obj.save(flush: true, failOnError: true) > > } > > > > Observed result: > > > > dirty=false > > dirty dateDisabled=false > > dirty usernameDisabled=false > > dirty properties=[] > > > > No UPDATE is generated. > > > > But this works: > > > > obj.description = 'changed' > > obj.dateDisabled = LocalDateTime.now() > > > > obj.save(flush: true, failOnError: true) > > > > And this also works: > > > > obj.dateDisabled = LocalDateTime.now() > > obj.markDirty() > > > > obj.save(flush: true, failOnError: true) > > > > Gianluca Sartori > > -- > > https://dueuno.com >
