Skip to content

@Validated occasionally throws ConstraintDeclarationException from Hibernate Validator on JDK 15 #26149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jo-ka opened this issue Nov 24, 2020 · 13 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression

Comments

@jo-ka
Copy link

jo-ka commented Nov 24, 2020

Spring Boot 2.2.6, Spring Cloud Greenwich.RELEASE (I have since realized that I should be using the Hoxton branch with 2.2.x, which I have upgraded to now)

Following the suggestion from https://stackoverflow.com/questions/57811421/how-to-validate-request-parameters-on-feign-client/61635351#61635351, I can successfully use a validated Feign interface like this:

@Validated
@FeignClient( name = "gps-service", path = "${infrastructure.base-path.gps-service}" )
public interface GpsClient
{
   @PostMapping( value = "/files/gpslogs" )
   GpsLogFileResult triggerGpsLogFile( @RequestBody @Valid @NotNull GpsLogFileParameters parameters );
}

That is, this works most of the time. Very rarely, but then seemingly in bursts, the call to triggerGpsLogFile fails due to an exception like shown below. This application runs in a Kubernetes cluster and there are usually many thousands of calls to this method per day without the exception. Then one day, there are suddenly around 100 cases of the ConstraintDeclarationException within a time span of 10 minutes, only to be gone again afterwards.

javax.validation.ConstraintDeclarationException: HV000152: Two methods defined in parallel types must not declare parameter constraints, if they are overridden by the same method, but methods $Proxy268#triggerGpsLogFile(GpsLogFileParameters) and GpsClient#triggerGpsLogFile(GpsLogFileParameters) both define parameter constraints.
	at org.hibernate.validator.internal.metadata.aggregated.rule.ParallelMethodsMustNotDefineParameterConstraints.apply(ParallelMethodsMustNotDefineParameterConstraints.java:23)
	at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:461)
	at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:377)
	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:204)
	at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:166)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:265)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:233)
	at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:105)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy269.triggerGpsLogFile(Unknown Source)
	at com.acme.application.service.GpsService.triggerGpsLogFile(GpsService.java:227)

GpsService.triggerGpsLogFile() makes the call to GpsClient.triggerGpsLogFile().

Having read the relevant Hibernate Validator documentation and the source code of ParallelMethodsMustNotDefineParameterConstraints, I understand that Bean Validation method parameter annotations on overridden/implementing methods may pose a problem.

And as @wilkinsona pointed out at spring-projects/spring-boot#17000, @Validated is realized by dynamic proxies (which can also be seen in the stacktrace above). So I kind of see why this could be a problem. But then I don't understand why the vast majority of calls work correctly, also throwing a ConstraintViolationException (note: Violation <-> Declaration) if applicable.

I couldn't find any official documentation about using @Validated on a @FeignClient, maybe that's for a reason and what I'm doing is basically unsupported?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 24, 2020
@jhoeller
Copy link
Contributor

There seems to be a Hibernate configuration property hibernate.validator.allow_parallel_method_parameter_constraint which can be set to true. I'm not sure where "parallel" methods (with a subtype method implementing two distinct method declarations from unrelated interfaces) come from here but I suppose that property would help for a start.

With respect to your scenario specifically, the JVM could potentially use different method handles for the proxy invocation, e.g. sometimes the interface method handle and sometimes the proxy class method handle. Maybe Hibernate Validator misidentifies a proxy class method handle as a parallel method hierarchy? Might be worth reporting to them as well.

@jhoeller jhoeller self-assigned this Nov 24, 2020
@jo-ka
Copy link
Author

jo-ka commented Nov 25, 2020

Thanks for your reply! I will test that configuration property and see how it goes (for some time, to be sure). I'll report back in a few weeks then. In addition, I'll raise an issue with Hibernate Validator.

@jo-ka
Copy link
Author

jo-ka commented Nov 25, 2020

Sorry for asking a potentially trivial question, but how could I set that property in my application.properties? Or would I need to use the programmatic bootstrapping for that (Hibernate Validator docs)?

@jhoeller
Copy link
Contributor

At the core level, we got a setValidationProperties method on LocalValidatorFactoryBean. @snicoll is there a straightforward way to set such validation properties in Boot, or would it simply require a custom LocalValidatorFactoryBean definition?

@snicoll
Copy link
Member

snicoll commented Nov 25, 2020

@snicoll is there a straightforward way to set such validation properties in Boot, or would it simply require a custom LocalValidatorFactoryBean definition?

At this time, a custom LocalValidatorFactoryBean is required. Our definition is pretty straightforward.

@jo-ka
Copy link
Author

jo-ka commented Nov 26, 2020

Thanks again to both of you for helping.

I was thinking that I'd like to keep the current Boot autoconfiguration (for one, so that the application behaves the same, and also so that any later modifications to the autoconfiguration will automatically become available to the application, too).

So I first tried to inject the LocalValidatorFactoryBean, apply the property there and return that as my own @Bean LocalValidatorFactoryBean. But that obviously failed due to a circular reference (BeanCurrentlyInCreationException). So instead I came up with this:

@Configuration
public class ValidatorConfig
{
   @Bean
   public LocalValidatorFactoryBean validatorAllowingParallelMethodParameterConstraints()
   {
      LocalValidatorFactoryBean defaultValidator = ValidationAutoConfiguration.defaultValidator();

      Properties properties = new Properties();
      properties.put( HibernateValidatorConfiguration.ALLOW_PARALLEL_METHODS_DEFINE_PARAMETER_CONSTRAINTS, "true" );
      defaultValidator.setValidationProperties( properties );

      return defaultValidator;
   }
}

Since ValidationAutoConfiguration is not in an "internal" package and I do want to hook into the autoconfiguration mechanism, it seems kind of okay to me to directly call ValidationAutoConfiguration.defaultValidator(). Of course, I trade benefitting from later updates to the autoconfiguration for dependency on the API like that.

@jhoeller jhoeller added in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Nov 30, 2020
@jhoeller jhoeller added this to the 5.3.2 milestone Nov 30, 2020
@jhoeller
Copy link
Contributor

A quick update: We'll try to do something about this by default.

Could you please confirm which JDK you're running on there? Is it by any chance >8 in which case it might be a regression that happens only on newer JDKs (while working reliably on 8)? Or are you seeing the effect on JDK 8 as well?

@jo-ka
Copy link
Author

jo-ka commented Nov 30, 2020

Those exceptions always occured on JDK 11. Unfortunately, I can't tell if it already happens with JDK 8 because we've been using 11 for a while already. And I'm afraid that trying to reproduce it now with 8 would prove to be difficult because it only happened so rarely (and that particular application requires 11+ anyway).

@jhoeller
Copy link
Contributor

jhoeller commented Nov 30, 2020

Thanks for the quick reply, that's very helpful for a start. We are seeing similar exceptions on our JDK 15 CI build at the moment, never seen on JDK 8 CI before, so it looks like a JDK 11+ regression to me.

@jhoeller jhoeller added type: regression A bug that is also a regression and removed type: bug A general bug labels Nov 30, 2020
@jhoeller jhoeller changed the title @Validated @FeignClient rarely throws ConstraintDeclarationException @Validated occasionally throws ConstraintDeclarationException with Hibernate Validator on JDK 11+ Nov 30, 2020
@jhoeller jhoeller changed the title @Validated occasionally throws ConstraintDeclarationException with Hibernate Validator on JDK 11+ @Validated occasionally throws ConstraintDeclarationException from Hibernate Validator on JDK 11+ Nov 30, 2020
@jo-ka
Copy link
Author

jo-ka commented Nov 30, 2020

My last comment wasn't correct: In fact, we've already been using JDK 15 as runtime environment for a couple of weeks, too. I was under the impression that the exceptions may have occured before we made the move from 11 to 15, but actually this is already outside our log retention period of 30 days.

So at this point, I cannot say if it was introduced with 11 or 15 (let alone 8), but we're now witnessing it with 15, too.

@jhoeller jhoeller changed the title @Validated occasionally throws ConstraintDeclarationException from Hibernate Validator on JDK 11+ @Validated occasionally throws ConstraintDeclarationException from Hibernate Validator on JDK 15 Nov 30, 2020
@jhoeller
Copy link
Contributor

jhoeller commented Dec 3, 2020

After debugging this down to the level of Class.isAssignableFrom in HV's MethodConfigurationRule.isDefinedOnParallelType having to misfunction in order for this to happen, it turns out that this is indeed a JVM bug that has been fixed for the yet-to-be-released JDK 15.0.2: https://bugs.openjdk.java.net/browse/JDK-8253566

From that perspective, we won't try to work around it in our code (and it seems we cannot anyway). Thanks for your help in narrowing this!

@jhoeller jhoeller closed this as completed Dec 3, 2020
@jhoeller jhoeller removed this from the 5.3.2 milestone Dec 3, 2020
@jhoeller
Copy link
Contributor

jhoeller commented Dec 4, 2020

BTW here's the Hibernate Validator ticket for it: https://hibernate.atlassian.net/browse/HV-1801?focusedCommentId=107317

Apparently there's a workaround: adding the JVM flags -XX:+UnlockDiagnosticVMOptions -XX:+ExpandSubTypeCheckAtParseTime

@abmjunaed
Copy link

I am having the same issue with openjdk:16-jdk-alpine in my docker image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

5 participants