xpap
xpap

Reputation: 33

Model validation exception with Spring Boot 2.0.2.RELEASE

I have some issue with migrating to Spring Boot 2.0.2.RELEASE. Seems that hibernate validator tries to validate default fileds of Enum Kotlin's type.

There is a simple test code:

gradle.build

buildscript {
    ext {
        kotlinVersion = '1.2.41'
        springBootVersion = '2.0.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

plugins {
    id "io.spring.dependency-management" version "1.0.5.RELEASE"
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = test
sourceCompatibility = 1.8
compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

repositories {
    mavenCentral()
}

dependencies {

    compile("org.springframework.boot:spring-boot-starter-web")
    compile('org.springframework.boot:spring-boot-starter-data-jpa')

    compile 'com.h2database:h2'
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    compile('org.jetbrains.kotlin:kotlin-reflect')

    testCompile('org.springframework.boot:spring-boot-starter-test')

}

Main class

@EnableJpaRepositories
@SpringBootApplication
class SimpleApplication

fun main(args: Array<String>) {
    run(SimpleApplication::class.java, *args)
}

Model with enumeration type

@Entity
data class SimpleModel(

    @Id
    @GeneratedValue
    var id: Long? = null,

    @get:NotEmpty
    @ElementCollection
    @JoinTable(name = "test_group", joinColumns = [JoinColumn(name = "id")])
    @Column(name = "group")
    @Enumerated(STRING)
        var groups: MutableSet<Group>? = null
)


enum class Group(val groupName: String, val groupDescription: String) {
    TEST1("TestGroup1", "Just for test"),
    TEST2("TestGroup2", "Just for test")
}

Test class

@RunWith(SpringRunner::class)
@TestConfiguration
@SpringBootTest(webEnvironment = RANDOM_PORT)
class SimpleServiceTest {

        @Autowired
        lateinit var applicationContext: ApplicationContext

        @Test
        fun testValidationError() {
            val model = SimpleModel().apply {
                groups = mutableSetOf(TEST1)
            }

            val resourceName = "${model.javaClass.simpleName.decapitalize()}.group[1]"

            val bindingResult = BeanPropertyBindingResult(TEST1, resourceName)

            val validator = applicationContext.getBean("mvcValidator", Validator::class.java)

            validator.validate(TEST1, bindingResult)
        } }

Test properties application.yml

spring:
  datasource:
    driver-class-name: org.h2.Driver
    jdbcUrl: jdbc:h2:mem:test
  jpa:
    properties:
      database: h2
      hibernate:
        hbm2ddl:
          ddl: true
          auto: create-drop
      javax:
        persistence:
          validation:
            mode: none

Exception message

java.lang.ArrayIndexOutOfBoundsException: 2

    at java.util.Arrays$ArrayList.get(Arrays.java:3841)
    at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:169)
    at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:435)
    at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:388)
    at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BuilderDelegate.build(BeanMetaDataImpl.java:788)
    at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BeanMetaDataBuilder.build(BeanMetaDataImpl.java:648)
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:192)
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.lambda$getBeanMetaData$0(BeanMetaDataManager.java:160)
    at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324)
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:159)
    at org.hibernate.validator.internal.engine.ValidationContext$ValidationContextBuilder.forValidate(ValidationContext.java:566)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:155)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:104)
    at org.springframework.boot.autoconfigure.validation.ValidatorAdapter.validate(ValidatorAdapter.java:64)
    at com.test.service.SimpleServiceTest.testValidationError(SimpleServiceTest.kt:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

This exception does not happen with Spring Boot 1.5.10.

Has anybody already had the same problem? I did not find any answer in the internet and here. Could someone help me, please?

Upvotes: 3

Views: 795

Answers (1)

Guillaume Smet
Guillaume Smet

Reputation: 10539

So the issue is the following:

  • the constructor as provided by the reflection API is: com.test.simple.Group(java.lang.String $enum$name, int $enum$ordinal, java.lang.String groupName, java.lang.String groupDescription) so it has four parameters. I suppose the name and the ordinal are passed to the constructor;
  • the parameter names as provided by KotlinReflectionParameterNameDiscoverer are only [groupName, groupDescription] (so not taking into account the additional parameters).

KotlinReflectionParameterNameDiscoverer should try to be consistent with what the Java reflection API returns. Otherwise it's definitely not usable as part of a HV ParameterNameProvider.

Upvotes: 2

Related Questions