Spring Boot/JPA/Hibernate annotations in Kotlin data classes

I’m still learning Kotlin, so I didn’t know this, but Kotlin has annotation use-site targets, like @get, @set, and @field.

I wanted to add timestamps via Hibernate, and they weren’t working. When I was trying to add a @(org.hibernate.annotations.)CreationTimestamp annotation and various other annotations without use-site targets, none of them were working. Couldn’t figure out why. Turns out, when you’re making a data class, you have to specify whether those annotations apply to getters, setters, fields, constructor parameters, etc.

A Kotlin data class that looks like this:

package com.example.test

import javax.persistence.*
import java.util.UUID
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp

@Entity
data class UselessThing(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID?,
    
    val name: String = "",
    
    @CreationTimestamp
    @Column(updatable = false)
    val createdDate: Date = Date(),
    
    @UpdateTimestamp
    val modifiedDate: Date = Date()
)

Would probably translate to something like this in Java:
// imports omitted

public class UselessThing {
  private UUID id;
  private String name;
  private Date createdDate = Date();
  private Date modifiedDate = Date();  

  // This is a constructor.
  public UselessThing(
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        UUID id,

        String name,

        @CreationTimestamp
        @Column(updatable = false)
        Date createdDate,

        @UpdateTimestamp Date modifiedDate) {
    this.id = id;
    this.name = name;
    this.createdDate = createdDate;
    this.modifiedDate = modifiedDate;
  }

  /* getters, setters, copy methods and whatever else
  Kotlin data classes make */
}

Instead, You’d create a Kotlin data class that looks like this:

package com.example.test

import javax.persistence.*
import java.util.UUID
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp

@Entity
data class UselessThing(
    @field: Id
    @field: GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID?,
    
    val name: String = "",
    
    @field: CreationTimestamp
    @field: Column(updatable = false)
    val createdDate: Date = Date(),
    
    @field: UpdateTimestamp
    val modifiedDate: Date = Date()
)

The latter, I’m guessing, probably translates to something roughly like this in Java:

// imports omitted

public class UselessThing {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private UUID id;
  private String name;

  @CreationTimestamp
  @Column(updatable = false)
  private Date createdDate = Date();

  @UpdateTimestamp
  private Date modifiedDate = Date();  

  public UselessThing(UUID id, String name, Date createdDate, Date modifiedDate) {
    this.id = id;
    this.name = name;
    this.createdDate = createdDate;
    this.modifiedDate = modifiedDate;
  }

  /* getters, setters, copy methods and whatever else
  Kotlin data classes make */
}

The latter, unlike the former, also works.

I encountered another gotcha with timestamps. When the client tried updating a UselessThing (via the .copy() method to create a new object with updated properties and JpaRepository’s save() method to persist the object) and didn’t specify a createdDate, the created_date column in the database would get updated every time the entity was updated.

Or something like that. Going over that again now, there must have been something else going on.

Anyway, adding the @Column(updatable = false) annotation fixed that issue.