To use spring persistence but maintain immutable types I've added the PersistenceConstructor annotation to my data classes. This tells spring to use that constructor when loading classes from the database.
However, I'm having trouble getting spring to find the constructor.
data class MyData @PersistenceConstructor constructor(@Id val id: Int? = null, val a:String)
This works 100% of the time on my machine but when deployed to heroku it consistently fails.
It looks like, by having default values for parameters kotlin generates more than one constructors but the problem is that each constructor get the annotation applied to them so it's just luck (or jdk implementation specific) which one spring picks up. The default one has no names for the parameters so Spring doesn't know what to do with it.
My real constructors are larger than this so it would be a pain to not have default values. Is there a way to get the annotation to only apply to the constructor without default values?
You can use the @JvmOverloads
annotation and this will automatically create permutations of the method that can be called by Java and also take advantage of the default values.
From the docs, this example:
@JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
...
}
will be seen from Java as:
// Java
void f(String a, int b, String c) { }
void f(String a, int b) { } // will default c
void f(String a) { } // will default b and c
Your case is a touch different where you have a defaulted parameter followed by one that is not. Simplifying your example to:
data class MyData @JvmOverloads constructor(val id: Int? = null, val a:String)
produces this view from Java:
// java
MyData(Int id, String a)
MyData(String a) // defaults id
You can read more about Java calling Kotlin interoperability in the Kotlin reference.
At the moment my current answer is to define two constructors. One for me to use that has defaults and one for spring to use that doesn't have defaults.
data class MyData @PersistenceConstructor constructor(val a: Int?, val b:String, val c : Collection<Int>) {
constructor(a: Int? = null, b: String = "", c: Collection<Int> = emptyList()) : this(a,b,c)
}
I don't like it as its duplication so it's not my preferred solution.