Skip to content
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

AOT processing for bean validation does not consider cascaded and container element constraints #33842

Closed
wilkinsona opened this issue Nov 4, 2024 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@wilkinsona
Copy link
Member

wilkinsona commented Nov 4, 2024

This affects 6.1.x and later. Here's a minimal Boot app that should illustrate the problem:

package com.example.gh_37101;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.BeanValidationPostProcessor;

import com.example.gh_37101.Gh37101Application.Example.Exclude;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Pattern;

@SpringBootConfiguration
public class Gh37101Application {

	@Bean
	static BeanValidationPostProcessor beanValidationPostProcess() {
		return new BeanValidationPostProcessor();
	}

	@Bean
	Example example() {
		Exclude exclude = new Exclude();
		exclude.setHttpStatus(List.of("invalid"));
		Example example = new Example();
		example.setExclude(List.of(exclude));
		return example;
	}

	public static void main(String[] args) {
		SpringApplication.run(Gh37101Application.class, args);
	}

	@Validated
	class Example {

		@Valid
		private List<Exclude> exclude = new ArrayList<>();
		
		public List<Exclude> getExclude() {
			return exclude;
		}

		public void setExclude(List<Exclude> exclude) {
			this.exclude = exclude;
		}

		public static class Exclude {

			private List<@Pattern(regexp="^([1-5][x|X]{2}|[1-5][0-9]{2})\\$") String> httpStatus;

			public List<String> getHttpStatus() {
				return httpStatus;
			}

			public void setHttpStatus(List<String> httpStatus) {
				this.httpStatus = httpStatus;
			}

		}

	}

}

Running it on the JVM will result in a start up failure as the validation fails:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v3.2.11)

2024-11-04T14:26:29.538Z  INFO 59233 --- [           main] c.example.gh_37101.Gh37101Application    : Starting Gh37101Application using Java 17.0.12 with PID 59233 (/Users/awilkinson/dev/temp/gh-37101/build/classes/java/main started by awilkinson in /Users/awilkinson/dev/temp/gh-37101)
2024-11-04T14:26:29.540Z  INFO 59233 --- [           main] c.example.gh_37101.Gh37101Application    : No active profile set, falling back to 1 default profile: "default"
2024-11-04T14:26:29.744Z  WARN 59233 --- [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'example' defined in com.example.gh_37101.Gh37101Application: Bean state is invalid: exclude[0].httpStatus[0].<list element> - must match "^([1-5][x|X]{2}|[1-5][0-9]{2})\$"
2024-11-04T14:26:29.762Z ERROR 59233 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'example' defined in com.example.gh_37101.Gh37101Application: Bean state is invalid: exclude[0].httpStatus[0].<list element> - must match "^([1-5][x|X]{2}|[1-5][0-9]{2})\$"
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971) ~[spring-context-6.1.14.jar:6.1.14]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.14.jar:6.1.14]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.11.jar:3.2.11]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.11.jar:3.2.11]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.2.11.jar:3.2.11]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.2.11.jar:3.2.11]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.2.11.jar:3.2.11]
        at com.example.gh_37101.Gh37101Application.main(Gh37101Application.java:35) ~[main/:na]
Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: exclude[0].httpStatus[0].<list element> - must match "^([1-5][x|X]{2}|[1-5][0-9]{2})\$"
        at org.springframework.validation.beanvalidation.BeanValidationPostProcessor.doValidate(BeanValidationPostProcessor.java:127) ~[spring-context-6.1.14.jar:6.1.14]
        at org.springframework.validation.beanvalidation.BeanValidationPostProcessor.postProcessBeforeInitialization(BeanValidationPostProcessor.java:91) ~[spring-context-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798) ~[spring-beans-6.1.14.jar:6.1.14]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.14.jar:6.1.14]
        ... 14 common frames omitted

Running as a native image will succeed:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v3.2.11)

2024-11-04T14:28:53.031Z  INFO 59751 --- [           main] c.example.gh_37101.Gh37101Application    : Starting AOT-processed Gh37101Application using Java 22.0.1 with PID 59751 (/Users/awilkinson/dev/temp/gh-37101/build/native/nativeCompile/gh-37101 started by awilkinson in /Users/awilkinson/dev/temp/gh-37101)
2024-11-04T14:28:53.032Z  INFO 59751 --- [           main] c.example.gh_37101.Gh37101Application    : No active profile set, falling back to 1 default profile: "default"
2024-11-04T14:28:53.038Z  INFO 59751 --- [           main] c.example.gh_37101.Gh37101Application    : Started Gh37101Application in 0.023 seconds (process running for 0.035)

Running as a native image succeeds because AOT processing does not generate any metadata for the @Pattern container element constraint. It's missed both because the cascade on exclude isn't considered and because container element constraints are not considered.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 4, 2024
@sdeleuze sdeleuze added type: bug A general bug theme: aot An issue related to Ahead-of-time processing and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Nov 5, 2024
@sdeleuze sdeleuze added this to the 6.1.15 milestone Nov 5, 2024
@sdeleuze sdeleuze added the in: core Issues in core modules (aop, beans, core, context, expression) label Nov 5, 2024
@sdeleuze sdeleuze self-assigned this Nov 5, 2024
@sdeleuze sdeleuze modified the milestones: 6.1.15, 6.2.0 Nov 12, 2024
@sdeleuze sdeleuze added type: enhancement A general enhancement and removed type: bug A general bug labels Nov 12, 2024
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Nov 13, 2024
This commit introduces support for bean validation container
element constraints, including cascaded ones.

Cascaded constraints in the parameterized types of a container
are not discoverable via the BeanDescriptor, so a complementary
type discovery is done on Spring side to cover the related use
case.

Closes spring-projectsgh-33842
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Nov 13, 2024
This commit introduces support for bean validation container
element constraints, including transitive ones.

Transitive constraints in the parameterized types of a container
are not discoverable via the BeanDescriptor, so a complementary
type discovery is done on Spring side to cover the related use
case.

Closes spring-projectsgh-33842
@wilkinsona
Copy link
Member Author

Thanks for this, @sdeleuze. Given that you consider this to be an enhancement, am I right to assume that it won't be backported to 6.1.x?

@sdeleuze
Copy link
Contributor

Yeah, the change is more involved than expected, so I chose to make it 6.2+ only after discussing with the team.

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) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants