0

Trying to run a very simple Spring Boot 3 app on GraalVM. The app just stores some data in H2 on startup. It is basically the same as what Josh does in the Spring Tips about Ahead-of-Time compilation, but using Kotlin instead of Java.

When starting the native image, I get the following error after Spring startup:

java.lang.UnsupportedOperationException: Kotlin class com.example.demo.basics.Customer has no .copy(…) method for property id
        at org.springframework.data.mapping.model.BeanWrapper$KotlinCopyUtil.setProperty(BeanWrapper.java:171) ~[na:na]
        at org.springframework.data.mapping.model.BeanWrapper.setProperty(BeanWrapper.java:79) ~[na:na]
...

I'm pretty new to GraalVM and Spring Native Images, don't know if I am missing something basic which needs to be configured when using Kotlin with Spring. The missing copy methods for the data class should have been generated by Kotlin, so I guess something is left out by the native compile related to Kotlin specifically.

The code which fails is the following class:

@Configuration
class BasicsConfiguration {
    @Bean // execute on application start
    fun basicsApplicationListener(customerRepository: CustomerRepository): ApplicationListener<ApplicationReadyEvent> {
        return ApplicationListener<ApplicationReadyEvent> {
            // store some values in the database
            customerRepository
                .saveAll(listOf("A", "B", "C").map { Customer(null, it) })
                .forEach { println(it) }
        }
    }
}

interface CustomerRepository : CrudRepository<Customer, Int>

data class Customer(@Id val id: Long?, val name: String)

Running the app on the JDK works perfectly fine: ./gradlew bootRun

2022-11-30T11:23:15.300+01:00  INFO 33997 --- [           main] com.example.demo.DemoApplicationKt       : Started DemoApplicationKt in 2.383 seconds (process running for 2.733)
Customer(id=1, name=A)
Customer(id=2, name=B)
Customer(id=3, name=C)

The native image is also created successfully: ./gradlew nativeCompile

Starting the native image works, the server process starts up but then fails:

2022-11-30T11:08:11.085+01:00  INFO 33059 --- [           main] com.example.demo.DemoApplicationKt       : Started DemoApplicationKt in 0.147 seconds (process running for 0.158)
2022-11-30T11:08:11.089+01:00 ERROR 33059 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.UnsupportedOperationException: Kotlin class com.example.demo.basics.Customer has no .copy(…) method for property id
        at org.springframework.data.mapping.model.BeanWrapper$KotlinCopyUtil.setProperty(BeanWrapper.java:171) ~[na:na]
        at org.springframework.data.mapping.model.BeanWrapper.setProperty(BeanWrapper.java:79) ~[na:na]
...
Henning Waack
  • 410
  • 3
  • 10
  • 1
    Looks like a consequence of the `copy` method not being configured for reflective access. Kotlin runtime probably uses reflection to look up methods, which does not work out of the box in native images – peterz Dec 01 '22 at 09:24
  • Yes, it works when I manually add the reflect-config for this data class, which is of course cumbersome. I opened an issue with the Spring team (https://github.com/spring-projects/spring-boot/issues/33485) – Henning Waack Dec 07 '22 at 14:02

1 Answers1

0

Seems like this is an issue with Spring Data, which is not providing all necessary reflection hints for the native image creation. Specifically the reflection configuration for data classes' copy$default methods are missing. Will (probably) be fixed with Spring 6.0.3 release, see this issue for details: https://github.com/spring-projects/spring-framework/issues/29593

Workaround: add something like this in your reflect-config.json for the given data class:

      {
        "name": "copy$default",
        "parameterTypes": [
          "com.example.demo.basics.Customer",
          "java.lang.Long",
          "java.lang.String",
          "int",
          "java.lang.Object"
        ]
      }
Henning Waack
  • 410
  • 3
  • 10