Spring Boot 3 with Kotlin throws java.lang.UnsupportedOperationException for data class on GraalVM - spring

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]
...

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"
]
}

Related

Bootstrapping Spring Data Neo4j repositories

I am starting with Spring Data Neo4j, using Spring Boot 3.0.0.
Created application with Spring Initializr, added a Node and a Repository.
Neo4jDemo,java
#SpringBootApplication
public class Neo4jDemo {
public static void main(String[] args) {
SpringApplication.run(Neo4jDemo.class, args);
}
}
Player.java
#Node("Player")
#RequiredArgsConstructor
public class Player {
#Id
private final String name;
}
PlayerRepository.java
public interface PlayerRepository extends ReactiveNeo4jRepository<Player, String> {
}
When I run this, It shows Finished Spring Data repository scanning message twice, with different repository count.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Neo4j repositories in DEFAULT mode.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 123 ms. Found 1 Neo4j repository interfaces.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Neo4j repositories in DEFAULT mode.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 2 ms. Found 0 Neo4j repository interfaces.
Am I missing something ?
Or just ignore these messages ?
Thanks
After adding following to application.properties, it shows Finished Spring Data repository scanning message, only once (with correct repository count)
application.properties
.
.
.
spring.data.neo4j.repositories.type = reactive
I guess, earlier it was looking for both (reactive and imperative) type of repositories.

Junit tests are failing after moving from spring boot 2.5.* to 2.6.*

I am working on moving my kafa CDC application to spring boot 2.6.* . But when I upgrade to spring boot 2.6.*, Junit tests are failing with following error
CdcConsumerApplicationTest > testMainApplicationLoad() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.ArrayStoreException at AnnotationParser.java:724
This is simple Junit example test that is failing
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = { AppTestConfiguration.class })
#EmbeddedKafka(partitions = 1, topics = { "XXX.SOME-DB2-CHANGE" })
public class CdcConsumerApplicationTest {
#Test
public void testMainApplicationLoad() {
}
}
If I run this tests locally on my IDE, they works fine but they are failing when running via jenkins. All my Junits are junit 5. Anything I am missing or I need to change when moving from spring boot 2.5 to 2.6 and further ?

No qualifying bean of type 'org.springframework.cloud.stream.binder.test.OutputDestination' available

I am trying to use spring cloud contracts for messaging.
I have a producer and a consumer who is publishing and consuming from rabbitmq.
they are working fine when I run both these spring boot applications.
however, when I try to mvn clean install , generated ContractVerifierTest fails with the error
2022-01-18 14:35:55.982 ERROR 62575 --- [ main] s.StreamOutputDestinationMessageReceiver : Exception occurred while trying to read a message from a channel with name [fraud]
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.stream.binder.test.OutputDestination' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) ~[spring-beans-5.3.14.jar:5.3.14]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.3.14.jar:5.3.14]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172) ~[spring-context-5.3.14.jar:5.3.14]
at org.springframework.cloud.contract.verifier.messaging.stream.StreamOutputDestinationMessageReceiver.receive(StreamOutputDestinationMessageReceiver.java:43) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
at org.springframework.cloud.contract.verifier.messaging.stream.StreamOutputDestinationMessageReceiver.receive(StreamOutputDestinationMessageReceiver.java:55) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
at org.springframework.cloud.contract.verifier.messaging.stream.StreamOutputDestinationMessageReceiver.receive(StreamOutputDestinationMessageReceiver.java:30) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
at org.springframework.cloud.contract.verifier.messaging.stream.StreamStubMessages.receive(StreamStubMessages.java:59) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
at org.springframework.cloud.contract.verifier.messaging.stream.StreamStubMessages.receive(StreamStubMessages.java:31) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
at org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging.receive(ContractVerifierMessaging.java:65) ~[spring-cloud-contract-verifier-3.1.0.jar:3.1.0]
I have spring-cloud-stream, spring-cloud-stream-binder-rabbit, spring-cloud-starter-contract-verifier, spring-cloud-stream:test-binder dependencies in my pom as well.
My spring boot application class is configured like this
#SpringBootApplication
#EnableBinding(Source.class)
public class ProducerApplication {
It is sending the message to the destination topic from tests as well. However, it fails to read it
#Test
public void validate_contract_name() throws Exception {
// when:
trigger(); // this happens fine
// then:
ContractVerifierMessage response = contractVerifierMessaging.receive("destination-name",
contract(this, "contract_name.yml")); // THIS FAILS
assertThat(response).isNotNull();
I have annotated my base test class with these
#SpringBootTest(classes = ProducerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
#ExtendWith(SpringExtension.class)
#AutoConfigureMessageVerifier
I have verified that OutputDestination class is available in the classpath.
I have org.springframework.cloud:spring-cloud-stream:test-jar:test-binder:3.2.1:test in the classpath.
spring-boot-starter-parent version is 2.6.2. spring cloud version is 2021.0.0
I have configured spring-cloud-contract-maven-plugin version 3.1.0 to use JUNIT5

DI in tests without using spring boot (#SpringBootTest)

Switching from spring boot back to "normal" spring because the app only uses some jdbc code to "upsert" into a postgresql database.
1)
tried annotating the test class with:
#RunWith(SpringJUnit4ClassRunner.class)
public class DBIntegration {
results in:
java.lang.IllegalStateException: Failed to load ApplicationContext
2)
tried annotating the class with:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {})
public class DBIntegration {
[main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [de.mydomain.myproject.DBIntegration]: no resource found for suffixes {-context.xml}.
No exceptions, but java.lang.Exception: No tests found matching [{ExactMatcher:fDisplayName=insertDataFrom_sometest],
3) tried annotating the class with:
#Component
public class DBIntegration {
dependency injection does not work in this case, the expected service
(to be injected) throws a nullpointerexception

Wiring Activiti as a Spring Bean in Grails

I'm trying to create a Grails application that uses Activiti for its process engine. To that end, I'd like the main Activiti service classes (RuntimeService, TaskService, etc.) to be wired as Spring beans.
I believe that I have the wiring setup correctly, but when I run a simple integration test that calls the runtime service, I get an error that Spring couldn't open a hibernate session (see Full Stack Trace, below).
Update: I can start the application with run-app, then call a controller action which calls my service, and it all works. So, the Activiti wiring works, it just has some conflict with the Grails Integration Testing mixin.
I really want the Activiti services to use the same datasource connection as the Grails application. I'm assuming that the problem is that Activiti is trying to create its own ConnectionHolder instance, when Grails already has one setup for the integration tests.
My specific (and possibly misguided) question I have is, how do I configure my Activiti ProcessEngine so that it uses the same data source and hibernate connection that my Grails application does?
The more general question is, how can I best make the Activiti services available to my Grails application? I've looked at the Activiti plugin for grails, and the source of it has helped me get this far. However, I'd rather not use that plugin; it's not using the latest activiti, development on it is not terribly active, and it's not really what I need in any case.
Full Stack Trace
| Failure: start approver request(com.package.MyServiceSpec)
| org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#7f2e1821] for key [org.springframework.jdbc.datasource.LazyConnectionDa
at grails.test.mixin.integration.IntegrationTestMixin.initIntegrationTest(IntegrationTestMixin.groovy:58)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.runtime.extension.builtin.JUnitFixtureMethodsExtension$FixtureType$FixtureMethodInterceptor.intercept(JUnitFixtureMethodsExtension.java:145)
at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:84)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
Caused by: org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#7f2e1821] for key [org.springframework.jdbc.datasource.LazyConn
... 7 more
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#7f2e1821] for key [org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy#1b7aeb] bound to thread [main]
... 7 more
| Completed 1 integration test, 1 failed in 0m 1s
resources.groovy
import org.activiti.engine.ProcessEngine
import org.activiti.spring.ProcessEngineFactoryBean
import org.activiti.explorer.form.*
import org.activiti.spring.SpringProcessEngineConfiguration
import grails.util.Environment
//These imports are only needed in the test environment for building an h2 database for activiti during unit tests
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.jdbc.datasource.SimpleDriverDataSource
beans = {
processEngineConfig(SpringProcessEngineConfiguration) {
dataSource = ref('dataSource')
transactionManager = ref('transactionManager')
databaseType = 'oracle'
databaseSchema = 'OURSCHEMA'
databaseSchemaUpdate = false
jobExecutorActivate = true
}
processEngine(ProcessEngineFactoryBean) {
processEngineConfiguration = ref("processEngineConfig")
}
runtimeService(processEngine: "getRuntimeService")
repositoryService(processEngine: "getRepositoryService")
taskService(processEngine: "getTaskService")
managementService(processEngine: "getManagementService")
historyService(processEngine: "getHistoryService")
formService(processEngine: "getFormService")
}
Service class
class MyService {
def foapAuthFoapService
def processEngine
def runtimeService
def repositoryService
def taskService
def managementService
def historyService
def formService
/**
* Start the activiti process.
*
*/
def startRequest(String requester, String subject, String designatedApprover) {
runtimeService.startProcessInstanceByKey('MyProcess', ["requester": requester, "subject": subject, "designatedApprover": designatedApprover])
}
}
Spock Test
def "start request"() {
setup:
def approverRequest = service.startRequest(requester, subject, designatedApprover)
def variables = runtimeService.getVariables(approverRequest.id) //approverRequest.getProcessVariables()
expect:
approverRequest instanceof ProcessInstance
variables.entrySet().contailsAll(["designatedApprover": designatedApprover, "requester": requester, "subject": subject].entrySet())
where:
requester | subject | designatedApprover
"abc123" | "def456"| "hij789"
}
we at Alephsa are maintaining the grails activiti plugin updated to the latest version of Activiti (5.17) and we are working to update to version 6. You can find the plugin at bintray also with the grails activiti spring security plugin. In our github account you can find the source code to guide you in your efforts.
Regards.

Resources