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

MediaType property binding issue in native builds #30491

Closed
mockxe opened this issue May 12, 2023 · 11 comments
Closed

MediaType property binding issue in native builds #30491

mockxe opened this issue May 12, 2023 · 11 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) theme: aot An issue related to Ahead-of-time processing type: bug A general bug
Milestone

Comments

@mockxe
Copy link

mockxe commented May 12, 2023

Affects: 6.0.8


issue description:
When setting a org.springframework.http.MediaType via config properties (e.g. application/json) it works well when running in jvm, for example by ./gradlew bootRun. But when running natively, for example by ./gradlew nativeRun it fails on startup with a property binding issue: failed to convert java.lang.String to org.springframework.http.MediaType (caused by java.lang.IllegalArgumentException: Invalid token character '/' in token "application/json") (full error below).
It is possible to fix the native version by specifying the media type as application-json but this let's the jvm version fail on startup with a similar issue.

This makes it hard to develop and debug on jvm and deploy as native image, when you use some general default config for both.

reproduce:
I encountered this when working with Spring Data REST, when I was setting spring.data.rest.default-media-type, but it also can be reproduced by setting up your own config properties which accept a MediaType.

full error when starting native build with application/json (bootRun works fine)

***************************
APPLICATION FAILED TO START
***************************

Description: Failed to bind properties under 'spring.data.rest.default-media-type' to org.springframework.http.MediaType:
Property: spring.data.rest.default-media-type
Value: "application/json"
Origin: class path resource [application.yml] - 13:27
Reason: failed to convert java.lang.String to org.springframework.http.MediaType (caused by java.lang.IllegalArgumentException: Invalid token character '/' in token "application/json")
Action: Update your application's configuration 

full error when starting jvm build with application-json (nativeRun works fine)

***************************
APPLICATION FAILED TO START
***************************

Description: Failed to bind properties under 'spring.data.rest.default-media-type' to org.springframework.http.MediaType:
Property: spring.data.rest.default-media-type
Value: "application-json"
Origin: class path resource [application.yml] - 13:27
Reason: failed to convert java.lang.String to org.springframework.http.MediaType (caused by org.springframework.util.InvalidMimeTypeException: Invalid mime type "application-json": does not contain '/')
Action: Update your application's configuration

possible workarounds:

  • in general, having multiple configs for developing (jvm) and deployment/production (native)
  • for this specific case with Spring Data REST, setting the media type programmatically via config.setDefaultMediaType()

If you like I can prepare a repository with a minimal test case.

Thanks for your work

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 12, 2023
@bclozel
Copy link
Member

bclozel commented May 12, 2023

In your case, it looks like the value given to MediaType for parsing is "application-json". This is of course an invalid media type and the exception thrown by Spring Framework is expected in this case. I'm not sure how this value is being parsed in the configuration though. Which Spring Boot version are you using? Can you reproduce that in a sample application and share it with us?

I've reproduced a slightly different problem with the latest Spring Boot 3.0.x by adding the GraalVM and spring-data-rest starter, and binding to this property:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'spring.data.rest.default-media-type' to org.springframework.http.MediaType:

    Property: spring.data.rest.default-media-type
    Value: "application/json"
    Origin: class path resource [application.yml] - 4:27
    Reason: failed to convert java.lang.String to org.springframework.http.MediaType (caused by java.lang.IllegalArgumentException: Invalid token character '/' in token "application/json")

Action:

Update your application's configuration

Here, the value is not incorrect but still cannot be converted to a MediaType instance. This looks like a configuration properties binding issue in native mode.

The detailed exception shows that binding to MediaType from application properties here fails because the binding process is using the MimeType default constructor which doesn't expect a full media type.

org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.data.rest.default-media-type' to org.springframework.http.MediaType
	at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:387) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:347) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$4(Binder.java:472) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:98) ~[na:na]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:86) ~[na:na]
	at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:62) ~[na:na]
	at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:476) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:590) ~[na:na]
	at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:576) ~[na:na]
	at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:474) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:414) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:343) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:332) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:262) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:249) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:93) ~[mediatype:na]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:96) ~[mediatype:na]
	at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:79) ~[mediatype:na]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:327) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:267) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory$1.orderedStream(DefaultListableBeanFactory.java:471) ~[na:na]
	at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.lambda$new$16(RepositoryRestMvcConfiguration.java:270) ~[mediatype:4.0.5]
	at org.springframework.data.util.Lazy.getNullable(Lazy.java:245) ~[mediatype:3.0.5]
	at org.springframework.data.util.Lazy.get(Lazy.java:114) ~[mediatype:3.0.5]
	at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.repositoryRestConfiguration(RepositoryRestMvcConfiguration.java:382) ~[mediatype:4.0.5]
	at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration__BeanDefinitions.lambda$getRepositoryRestConfigurationInstanceSupplier$7(RepositoryRestMvcConfiguration__BeanDefinitions.java:191) ~[na:na]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:171) ~[na:na]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:204) ~[na:na]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:216) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:204) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:327) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:267) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:327) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:267) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:327) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:267) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:663) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:651) ~[mediatype:6.0.8]
	at org.springframework.boot.convert.ApplicationConversionService.addBeans(ApplicationConversionService.java:276) ~[na:na]
	at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.addFormatters(WebMvcAutoConfiguration.java:319) ~[mediatype:na]
	at org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.addFormatters(WebMvcConfigurerComposite.java:81) ~[na:na]
	at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration.addFormatters(DelegatingWebMvcConfiguration.java:78) ~[mediatype:6.0.8]
	at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration.mvcConversionService(WebMvcAutoConfiguration.java:511) ~[mediatype:na]
	at org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration__BeanDefinitions$EnableWebMvcConfiguration__BeanDefinitions.lambda$getMvcConversionServiceInstanceSupplier$6(WebMvcAutoConfiguration__BeanDefinitions.java:182) ~[na:na]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:171) ~[na:na]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:204) ~[na:na]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[mediatype:6.0.8]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:216) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:204) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:327) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:267) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1158) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[mediatype:6.0.8]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[mediatype:6.0.8]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[mediatype:6.0.8]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[mediatype:6.0.8]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[mediatype:3.0.6]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[mediatype:3.0.6]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[mediatype:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[mediatype:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[mediatype:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[mediatype:3.0.6]
	at com.example.mediatype.MediatypeApplication.main(MediatypeApplication.java:10) ~[mediatype:na]
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.springframework.http.MediaType] for value [application/json]
	at org.springframework.core.convert.support.ObjectToObjectConverter.convert(ObjectToObjectConverter.java:117) ~[na:na]
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[na:na]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[mediatype:6.0.8]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:109) ~[na:na]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:100) ~[na:na]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:92) ~[na:na]
	at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:459) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:403) ~[mediatype:3.0.6]
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:343) ~[mediatype:3.0.6]
	... 161 common frames omitted
Caused by: java.lang.IllegalArgumentException: Invalid token character '/' in token "application/json"
	at org.springframework.util.MimeType.checkToken(MimeType.java:224) ~[mediatype:6.0.8]
	at org.springframework.util.MimeType.<init>(MimeType.java:183) ~[mediatype:6.0.8]
	at org.springframework.util.MimeType.<init>(MimeType.java:134) ~[mediatype:6.0.8]
	at org.springframework.util.MimeType.<init>(MimeType.java:123) ~[mediatype:6.0.8]
	at org.springframework.http.MediaType.<init>(MediaType.java:478) ~[mediatype:6.0.8]
	at java.base@17.0.5/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[mediatype:na]
	at java.base@17.0.5/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[mediatype:na]
	at org.springframework.core.convert.support.ObjectToObjectConverter.convert(ObjectToObjectConverter.java:113) ~[na:na]
	... 169 common frames omitted

I'm moving this issue to Spring Boot for now - if it turns out this is not a binder issue, but a more general conversion issue in Spring Framework we will move it back here.

@bclozel bclozel transferred this issue from spring-projects/spring-framework May 12, 2023
@mockxe
Copy link
Author

mockxe commented May 12, 2023

hi and thanks for moving the issue, I wasn't sure where to put it.

regarding your comment: this is exactly the issue I was referring to. Intuitively I would configure a media type with a slash / (e.g. application/json), this is also what my IDE suggests. This works well when doing a classic jvm build&run. I use ./gradlew bootRun.

I also added GraalVM ("GraalVM Native Support", as it's called in Spring Initializr), which gives me the additional gradle task nativeRun. When I use this (or any other build that incorporates GraalVM native image build) to run the application, I get the same binding issue that you got.

Apparently, when using a native build, the constructor expects a media type with a dash - (e.g. application-json), the second option I proposed in my initial report. This works well in native builds, but is not compatible with jvm builds.

Ideally jvm and native build would use the same constructor to accept the same format.

I put together a minimal test case for you: https://github.com/mockxe/native-property-issue
It was created using Spring Initializr with framework version 6.0.8 and boot version 3.0.9. I'm on a Liberica Native Image Kit

openjdk version "17.0.6" 2023-01-17 LTS
OpenJDK Runtime Environment GraalVM 22.3.1 (build 17.0.6+10-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.1 (build 17.0.6+10-LTS, mixed mode, sharing)

Please see the test cases README.md for further information

I hope this information helps you.

@wilkinsona
Copy link
Member

On the JVM, the creation of the MediaType is performed by MediaTypeEditor:

"main" spring-projects/spring-boot#1 prio=5 os_prio=31 cpu=4988.82ms elapsed=183.75s tid=0x00007fd37a00b000 nid=0x2203 at breakpoint [0x0000700000992000]
   java.lang.Thread.State: RUNNABLE
	at org.springframework.http.MediaType.<init>(MediaType.java:557)
	at org.springframework.http.MediaType.parseMediaType(MediaType.java:745)
	at org.springframework.http.MediaTypeEditor.setAsText(MediaTypeEditor.java:37)
	at org.springframework.beans.TypeConverterDelegate.doConvertTextValue(TypeConverterDelegate.java:425)
	at org.springframework.beans.TypeConverterDelegate.doConvertValue(TypeConverterDelegate.java:398)
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:155)
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:73)
	at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:45)
	at org.springframework.boot.context.properties.bind.BindConverter$TypeConverterConverter.convert(BindConverter.java:221)
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192)
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:109)
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:100)
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:92)
	at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:459)
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:403)
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:343)
	…

@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels May 13, 2023
@firasrg
Copy link

firasrg commented May 13, 2023

Hello! I'd like to contribute here, it's my first time 😇. Is it possible ?

@quaff
Copy link
Contributor

quaff commented May 15, 2023

ObjectToObjectConverter is used if spring-boot-starter-data-rest present

     Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.springframework.http.MediaType] for value [application/json]
       org.springframework.core.convert.support.ObjectToObjectConverter.convert(ObjectToObjectConverter.java:117)
       org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
       org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192)
       org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:109)
       org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:100)
       [...]
     Caused by: java.lang.IllegalArgumentException: Invalid token character '/' in token "application/json"
       org.springframework.util.MimeType.checkToken(MimeType.java:224)
       org.springframework.util.MimeType.<init>(MimeType.java:183)
       org.springframework.util.MimeType.<init>(MimeType.java:134)
       org.springframework.util.MimeType.<init>(MimeType.java:123)
       org.springframework.http.MediaType.<init>(MediaType.java:478)
       [...]

ConverterNotFoundException is thrown if we change spring-boot-starter-data-rest to spring-boot-starter-web

     Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [org.springframework.http.MediaType]
       org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:118)
       org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:100)
       org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:92)
       org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:459)
       org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:403)
       [...]

@wilkinsona
Copy link
Member

The problem's occurring because MediaTypeEditor is hidden due to a lack of reflection hint metadata for it. This prevents BeanUtils.findEditorByConvention(Class<?>) from creating and returning a MediaTypeEditor instance. A workaround is to provide a RuntimeHintsRegistar that registers the required reflection hint:

static class PropertyEditorRuntimeHints implements RuntimeHintsRegistrar {

	@Override
	public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
		hints.reflection().registerType(MediaTypeEditor.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
	}
		
}

This registrar must be imported using @ImportRuntimeHints(PropertyEditorRuntimeHints.class).

@bclozel bclozel transferred this issue from spring-projects/spring-boot May 15, 2023
@bclozel bclozel added this to the 6.0.x milestone May 15, 2023
@bclozel bclozel added the theme: aot An issue related to Ahead-of-time processing label May 15, 2023
@sdeleuze sdeleuze self-assigned this May 15, 2023
@sdeleuze sdeleuze modified the milestones: 6.0.x, 6.0.10 May 15, 2023
@quaff
Copy link
Contributor

quaff commented May 15, 2023

The problem's occurring because MediaTypeEditor is hidden due to a lack of reflection hint metadata for it. This prevents BeanUtils.findEditorByConvention(Class<?>) from creating and returning a MediaTypeEditor instance. A workaround is to provide a RuntimeHintsRegistar that registers the required reflection hint:

static class PropertyEditorRuntimeHints implements RuntimeHintsRegistrar {

	@Override
	public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
		hints.reflection().registerType(MediaTypeEditor.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
	}
		
}

This registrar must be imported using @ImportRuntimeHints(PropertyEditorRuntimeHints.class).

It doesn't explain the difference I posted above.

@wilkinsona
Copy link
Member

@quaff I'm not sure how that difference relates to this issue. If you don't use Spring Data REST then the spring.data.rest.default-media-type property will no longer be bound. Without some more information, it's not clear to me how the two different exceptions can occur. Perhaps you can share some more information that demonstrates the connection?

@quaff
Copy link
Contributor

quaff commented May 16, 2023

spring.data.rest.default-media-type

@wilkinsona I'm using @mockxe's project, but spring.data.rest.default-media-type deleted and only foo.media-type=application/json is used, if you change the dependency spring-boot-starter-data-rest to spring-boot-starter-web, the exception changed also.

@wilkinsona
Copy link
Member

Thanks, @quaff. With Spring Data REST on the classpath, the following reflection hints are generated:

  {
    "name": "org.springframework.http.MediaType",
    "allDeclaredConstructors": true,
    "allDeclaredFields": true,
    "methods": [
      {
        "name": "getQualityValue",
        "parameterTypes": [ ]
      }
    ]
  }

When spring-boot-starter-web is used instead, these hints are not generated. Without reflective access to the constructors, ObjectToObjectConverter won't try (and fail) to reflectively create a MediaType instance. I don't have time at the moment to track down the exact source of these hints but I suspect they're coming from Spring Data REST or Spring HATEOAS.

@jhoeller jhoeller added the in: web Issues in web modules (web, webmvc, webflux, websocket) label May 23, 2023
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Jun 9, 2023
@sdeleuze
Copy link
Contributor

sdeleuze commented Jun 9, 2023

I was a bit nervous adding hints for all PropertyEditor given the fact there are a lot of them and they are not all frequently needed, so for now let's introduce them on per use case basis when reported by users.

sbrannen added a commit that referenced this issue Jun 13, 2023
The dependency on spring-web from spring-beans makes it impossible to
import the projects in Eclipse IDE due to cycles between projects.

This commit therefore moves the web-related test for
BeanUtilsRuntimeHints to spring-web.

See gh-30491
mdeinum pushed a commit to mdeinum/spring-framework that referenced this issue Jun 29, 2023
mdeinum pushed a commit to mdeinum/spring-framework that referenced this issue Jun 29, 2023
The dependency on spring-web from spring-beans makes it impossible to
import the projects in Eclipse IDE due to cycles between projects.

This commit therefore moves the web-related test for
BeanUtilsRuntimeHints to spring-web.

See spring-projectsgh-30491
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) theme: aot An issue related to Ahead-of-time processing type: bug A general bug
Projects
None yet
Development

No branches or pull requests

8 participants