When I try to use @Value annotation a record property, I get a warning in the logs that autowiring should only be used on methods with parameters. What that seems to imply is that attaching @Value on the property is actually attaching it on the generated method property(), which has no parameters.

Example:

@Component
public record MyRecord(@Value("${myProp}") String prop) {}

Output:

02:21:15.043 [main] INFO  o.s.b.f.a.AutowiredAnnotationBeanPostProcessor - Autowired annotation should only be used on methods with parameters: public java.lang.String com.example.MyRecord.prop()

My fix is to just check first, if the bean is a record or not.

Comment From: snicoll

Thanks for the PR but are you sure that the code you've shared above currently triggers the warning? Does it work at all?

It fails for me as follows:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myComponent': Injection of autowired dependencies failed; nested exception is java.lang.IllegalAccessException: Can not set final java.lang.String field com.example.demo.MyComponent.prop to java.lang.String
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:921) ~[spring-context-5.3.29.jar:5.3.29]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.29.jar:5.3.29]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.14.jar:2.7.14]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.14.jar:2.7.14]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.14.jar:2.7.14]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.14.jar:2.7.14]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.14.jar:2.7.14]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.14.jar:2.7.14]
    at com.example.demo.DemoApplication.main(DemoApplication.java:12) ~[classes/:na]
Caused by: java.lang.IllegalAccessException: Can not set final java.lang.String field com.example.demo.MyComponent.prop to java.lang.String
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76) ~[na:na]
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80) ~[na:na]
    at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79) ~[na:na]
    at java.base/java.lang.reflect.Field.set(Field.java:799) ~[na:na]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:646) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.29.jar:5.3.29]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.29.jar:5.3.29]
    ... 17 common frames omitted

Turning a record into a bean is a bit odd. In any case, please clarify your assumptions as we can't really process this until we understand it.

Comment From: quaff

@snicoll I confirm that log is triggered.

 o.s.b.f.a.AutowiredAnnotationBeanPostProcessor INFO Autowired annotation should only be used on methods with parameters: public java.lang.String io.example.showcase.MyRecord.prop()

Comment From: snicoll

Thanks. "logging a warning" broke my brain, I was looking for a warning, not an info message. The bits on testing and use of record remains though.

Comment From: martin-lukas

@snicoll Sorry for the misunderstanding, it is an INFO log, but communicating a sort of warning, didn't know how to call it.

It works well for me with the code I provided (+ basic Spring Boot project, latest, JDK 21 or 17).

As for record being odd when used as a bean. I was just experimenting with records + Spring. Using records as a bean seems mostly equivalent to e.g.:

@Component
public class MyClass {
  private String prop;
  public MyClass(@Value("${myProp}") String prop) {
    this.prop = prop;
  }
  // getter
}

But you're kinda right. Now that I think about the use cases, it might not be that needed. If I have some service that needs a property value, and I use a record for it, I'd get an unwanted property accessor automatically, which is not great. I was thinking more like loading properties into a "configuration" carrying bean, but that's also possible with e.g.:

@ConfigurationProperties(prefix = "config")
public record Config(String first, String second) {}

which works well.

Comment From: snicoll

I was thinking more like loading properties into a "configuration" carrying bean,

Yeah, sorry. You're totally right. The @Value with a record makes sense indeed.

Comment From: snicoll

@martin-lukas thanks very much for making your first contribution to Spring Framework.