JPA / Hibernate is Inserting NULL When Default Value is Set on Field

Turns out that the fault didn't lie with Hibernate, but with Lombok. If you're using annotations such as @Data, @EqualsAndHashCodeOf, @NoArgsConstructor, @AllArgsConstructor, or @Builder, your domain object may be using Lombok. (Groovy has some similar annotations, just check if your imports are using groovy.transform or lombok as the package prefix)

The bit of code causing my problems was in my test class:

MyObject.builder().name(NAME).username(USERNAME).build();

After much trial and error, it became apparent that while my JPA/Hibernate setup was correct,the initialization default:

private Boolean activeIndicator = Boolean.TRUE;

was not being executed.

It took some digging, but apparently Lombok's Builder does not use Java's initialization, but its own means to create the object. This means that initialization defaults are overlooked when you call build(), hence the field will remain NULL if you don't explicitly set it in your builder chain. Note that normal Java initialization and constructors are unaffected by this; the problem ONLY manifests itself when creating objects with Lombok's builder.

The issue detailing this problem and Lombok's explanation on why they won't implement a fix is detailed here: https://github.com/rzwitserloot/lombok/issues/663#issuecomment-121397262

There are many workarounds to this, and the one you may want to use depends on your needs.

  • Custom Constructor(s): One can create custom constructors either in place of, or alongside the builder. When you need defaults to be set, use your constructors instead of the builder. - This solution makes it seem like you shouldn't even bother with a builder. The only reason you may want to keep the builder is if the builder is capable of delegating creation to your constructors, therefore getting the default values initialized when it builds. I have no idea if that's possible as I haven't tested it myself and I decided to go a different route. Do respond if you've looked into this option further.
  • Overriding the Build Method: If you're going to be using the Builder extensively and don't want to be writing a bunch of constructors, this solution is for you. You can set defaults in your own implementation of the build method and delegate to the super to do the actual build. Just be sure to document your code well so future maintainers know to set defaults on both the object and in the build method so they're set no matter how the object is initialized.
  • JPA PrePersist Annotation: If your requirements for defaults are for database constraints and you're using JPA or Hibernate, this option is available to you. This annotation will run the method it's attached to before every insert and update of the entity, regardless of how it was initialized. This is nice so that you don't have to set defaults in two places like you'd have to with the build override strategy. I went with this option as my business requirements have a strong defaulting requirement on this field. I added the code below to my entity class to fix the problem. I also removed the "= Boolean.TRUE" part of the isActive field declaration as it was no longer necessary.

    @PrePersist
    public void defaultIsActive() {
        if(isActive == null) {
            isActive = Boolean.TRUE;
        }
    }