31

I have an issue where Spring @Value annotation not working in the constructor.

I have this Email class that is reading some email related configuration.

In the constructor, if I put a break-point the values are empty

But if I call the sendEmail method, there are values.

The other class:

@Autowired
Email sender;

Email class:

@Component
public class Email{

    @Value("${TO_EMAIL}")
    private String toEmail;

    public Email() {        
        // Here toEmail is null 
    }

    public void sendEmail(String subject, String body) {
        // Here toEmail has value
    }
}
Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
JavaSheriff
  • 7,074
  • 20
  • 89
  • 159

3 Answers3

55

As far I remember, the value injection occurs after the constructor call.

Try to change your constructor to this:

public Email(@Value("${TO_EMAIL}") String toEmail) { 
   this.toEmail = toEmail;
}
Saurav Sahu
  • 13,038
  • 6
  • 64
  • 79
cvdr
  • 939
  • 1
  • 11
  • 18
  • 1
    Yes, this is definitely the problem. Spring can't inject values into an object until it has instantiated it. Either do what @cvdr is suggesting, or somehow delay the use of that value until after the application context has been fully constructed (all injection has occurred). – CryptoFool May 07 '19 at 22:08
  • 1
    Its working, great solution but i don't understand why would there be a difference between the class initialization and constructor parameter initialization – JavaSheriff May 08 '19 at 13:39
  • 1
    im not 100% sure, but when you annoted with @component the instance is create at spring startup, but the value injection dont occurs in that moment, in fact this happen after. When you put value on constructor you say "hey spring, i need this value on object creation". – cvdr May 08 '19 at 18:20
  • 1
    Bit late but... It is basically Java related - if there is no Object you can't set its values new invokes the constructor and if the @Value annotation is not supplied there the DI will occur after construction. There are ways to implement this DI mechanic with the mirroring api but that would be crazy and may slow down everything. On the other hand the spring boot documentation recommends to inject in the constructor due to possible side effects. PS: Same Problem need values in the constructor but don't want to let the signature explode.. But yeah only way afaik. – sascha10000 Sep 10 '21 at 15:37
10

The value injection occures after the constructor call, to solve this in your case, you can leave the constructor empty. And add a method annotated with "@PostConstructor". This way, the empty constructor will be called, then the values will be injected, then the @PostConstructor method will be called.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Omar Smri
  • 195
  • 1
  • 7
6

I encountered similar problems when I work with ApplicationRunner

public class AppStartupRunner implements ApplicationRunner {
    @Autowired
    private Environment myEnv; //not work

    @value(${xxx.xxx}) 
    private String myValue //not work

    @Autowired
    public AppStartupRunner(Environment env) {
      System.out.println(myEnv); //null
      System.out.println(myValue); //null
    }
}

After I changed to the below codes, it works perfectly

  @Autowired
  public AppStartupRunner(Environment env) {
      env.getProperty("key") //works!
  }
Vikki
  • 1,897
  • 1
  • 17
  • 24