Search code examples
javaspringspring-bootspring-mvcspring-annotations

@Value property is returning null in the spring component


I have created a validator component in spring boot and I am keeping a regex expression in the application.properties. I have used @Value annotation to get the value of regex in my component and I am compiling the Pattern outside any method or constructor and that is giving me null pointer exception as the regex is not getting it's value at that time. But when I move the pattern to some method, it's working fine. Why is that? Why is @Value not working even though object is created using @Component

Look at the code below:

Code returning NullPointerException:

@Component
public class ValidString implements ConstraintValidator<ValidString, String> {

  @Value("${user.input.regex}")
  private String USER_INPUT_REGEX;

  private Pattern USER_INPUT_PATTERN = Pattern.compile(USER_INPUT_REGEX);

  @Override
  public boolean validate(String userInput, ConstraintValidatorContext constraintValidatorContext) {
    return USER_INPUT_PATTERN.matcher(userInput).find();
  }
}

Code working fine:

@Component
public class ValidString implements ConstraintValidator<ValidString, String> {

  @Value("${user.input.regex}")
  private String USER_INPUT_REGEX;

  private Pattern USER_INPUT_PATTERN;

  @Override
  public boolean validate(String userInput, ConstraintValidatorContext constraintValidatorContext) {
    USER_INPUT_PATTERN = Pattern.compile(USER_INPUT_REGEX);
    return USER_INPUT_PATTERN.matcher(userInput).find();
  }
}

Also if you could explain why the first one is not working and second one is working, that'd be great.

application.properties

user.input.regex = ^[a-zA-Z0-9/\\-_ \\s+]*$

Solution

  • Field initializers (first example in question) are executed during the class constructor execution. @Value is injected by Spring after the constructor returns, using reflection. This means that you cannot have initializers using @Value-injected values.

    The issue can be resolved by constructor or setter injection:

    // Inject using constructor
    @Component
    public class ValidString implements ConstraintValidator<ValidString, String> {
    
      private Pattern USER_INPUT_PATTERN;
    
      @Autowired
      public ValidString(@Value("${user.input.regex}") String regex) {
        this.USER_INPUT_PATTERN = Pattern.compile(regex);
      }
    
    // Inject using setter method
    @Component
    public class ValidString implements ConstraintValidator<ValidString, String> {
    
      private Pattern USER_INPUT_PATTERN;
    
      @Autowired
      private void setUserInputRegex(@Value("${user.input.regex}") String regex) {
        this.USER_INPUT_PATTERN = Pattern.compile(regex);
      }