48

I am using Spring MVC. I have a UserService class annotated with @Service that has a lot of static variables. I would like to instantiate them with values from the application.properties file.

For example in application.properties I have: SVN_URL = http://some.url/repositories

Then in the class there is: @Value("${SVN_URL}") private static String SVN_URL

I get the Instantiation of bean failed; nested exception is java.lang.ExceptionInInitializerError

I have also tried @Autowired private static Environment env;

And then: private static String SVN_URL=env.getProperty("SVN_URL");

It gives the same error.

dannemp
  • 779
  • 1
  • 12
  • 32
  • Do you have no possibility to remove the static keyword from those variables? – dunni Jul 19 '17 at 13:51
  • My requirement was to use static field inside @ BeforeClass in Junit and I couldn't get spring profile @Value to be read and passed correctly. Finally I used @ Before to read the static value. It seems @ BeforeClass is executed first before even reading spring properties. – Smart Coder Jul 22 '21 at 19:49

5 Answers5

72

Think about your problem for a second. You don't have to keep any properties from application.properties in static fields. The "workaround" suggested by Patrick is very dirty:

  • you have no idea when this static field is modified
  • you don't know which thread modifies it's value
  • any thread at any time can change value of this static field and you are screwed
  • initializing private static field that way has no sense to me

Keep in mind that when you have bean controlled by @Service annotation you delegate its creation to Spring container. Spring controls this bean lifecycle by creating only one bean that is shared across the whole application (of course you can change this behavior, but I refer to a default one here). In this case any static field has no sense - Spring makes sure that there is only one instance of UserService. And you get the error you have described, because static fields initialization happens many processor-cycles before Spring containers starts up. Here you can find more about when static fields are initialized.

Suggestion

It would be much better to do something like this:

@Service
public class UserService {
    private final String svnUrl;

    @Autowired
    public UserService(@Value("${SVN_URL}") String svnUrl) {
        this.svnUrl = svnUrl;
    }
}

This approach is better for a few reasons:

  • constructor injection describes directly what values are needed to initialize the object
  • final field means that this value wont be changed after it gets initialized in a constructor call (you are thread safe)

Using @ConfigurationProperties

There is also another way to load multiple properties to a single class. It requires using prefix for all values you want to load to your configuration class. Consider following example:

@ConfigurationProperties(prefix = "test")
public class TestProperties {

    private String svnUrl;

    private int somePort;

    // ... getters and setters
}

Spring will handle TestProperties class initialization (it will create a testProperties bean) and you can inject this object to any other bean initialized by Spring container. And here is what exemplary application.properties file look like:

test.svnUrl=https://svn.localhost.com/repo/
test.somePort=8080

Baeldung created a great post on this subject on his blog, I recommend reading it for more information.

Alternative solution

If you need somehow to use values in static context it's better to define some public class with public static final fields inside - those values will be instantiated when classloader loads this class and they wont be modified during application lifetime. The only problem is that you won't be able to load these values from Spring's application.properties file, you will have to maintain them directly in the code (or you could implement some class that loads values for these constants from properties file, but this sounds so verbose to the problem you are trying to solve).

Szymon Stepniak
  • 36,710
  • 10
  • 91
  • 120
  • In fact initially I had private static final variables with the values directly in the code, I guess I'll just stick with this approach as the number of variables is too big – dannemp Jul 19 '17 at 14:23
  • You can use configuration class approach. I will update my answer in a minute. – Szymon Stepniak Jul 19 '17 at 14:25
  • From the fact that a Service class will have only one object instance, I guess there is no sense to use static methods inside of it? – dannemp Jul 19 '17 at 14:27
  • Correct. Static field makes sense if it's `final` (so it cannot be modified) and you see a use case when other class may also use this value for some reason. Never, I repeat, never make static fields mutable, otherwise you will run into big trouble. – Szymon Stepniak Jul 19 '17 at 14:28
  • @SzymonStepniak won't you get `final field not initialized` warning/error in the first snippet? – WesternGun Jul 16 '18 at 13:25
  • 1
    @WesternGun No, there is no such problem, because `svnUrl` field gets initialized in the class constructor. In this case we are using constructor injection that allows us to define a final field. The error you mention would exist if we use field or setter injection - in this case injection happens after object gets initialized. – Szymon Stepniak Jul 16 '18 at 13:47
26

Spring does not allow to inject value into static variables.

A workaround is to create a non static setter to assign your value into the static variable:

@Service
public class UserService {

    private static String SVN_URL;

    @Value("${SVN_URL}")
    public void setSvnUrl(String svnUrl) {
        SVN_URL = svnUrl;
    }

}
Patrick
  • 11,357
  • 14
  • 68
  • 108
7

Accessing application.properties in static member functions is not allowed but here is a work around,

application.properties

server.ip = 127.0.0.1

PropertiesExtractor.java

public class PropertiesExtractor {
     private static Properties properties;
     static {
        properties = new Properties();
        URL url = new PropertiesExtractor().getClass().getClassLoader().getResource("application.properties");
        try{
            properties.load(new FileInputStream(url.getPath()));
           } catch (FileNotFoundException e) {
                e.printStackTrace();
           }
        }

        public static String getProperty(String key){
            return properties.getProperty(key);
        }
}

Main.class

public class Main {
    private static PropertiesExtractor propertiesExtractor;
    static{
         try {
             propertiesExtractor = new PropertiesExtractor();
         } catch (UnknownHostException e) {
               e.printStackTrace();
           }
    }

    public static getServerIP(){
        System.out.println(propertiesExtractor.getProperty("server.ip")
    }
}
Jalaz Kumar
  • 73
  • 1
  • 6
  • If we read the properties like this can they be automatically override by setting environment variable? – niklodeon Jul 31 '20 at 10:55
  • i second Nikhil's question... pls tell the answer :) – user1955934 Oct 30 '20 at 10:25
  • 1
    No, they can't, you actually are only reading the application.properties file, by any chance, if you are overriding them with environment variables or spring's config server, those changes won't be noticed – Luiz Damy Mar 12 '21 at 23:40
1
static String profile;

@Value("${spring.profiles.active:Unknown}")
private void activeProfile(String newprofile) {
    profile = newprofile;
};
Smart Coder
  • 931
  • 8
  • 15
1

In order to gain static access to Spring Boot properties you can create a Properties Holder Component which implements the Command Line Runner interface. The command line runner interface executes run() upon component instantiation by Spring Boot.

Since we have autowired access to our properties object in the PropertiesHolder component, it is possible to assign the autowired properties to a static Properties class variable upon CommandLineRunner execution of the run() method.

At this point any class can statically call PropertiesHolder.getProperties() to access the contents of Spring Boot properties values.

PropertiesHolder.class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class PropertiesHolder implements CommandLineRunner {

    //Spring Boot Autowired Properties Object
    @Autowired MyProperties myAutowiredProperties;

    //Statically assigned Properties Object
    private static MyProperties properties;

    //Hide constructor (optional)
    private PropertiesHolder(){}

    public static MyProperties getProperties() throws NullPointerException{
        if(PropertiesHolder.properties == null)
            throw new NullPointerException("Properites have not been initialized by Spring Application before call.");

        return PropertiesHolder.properties;
    }

    //Use to assign autowired properties to statically allocated properties
    public static void makeAvailable(MyProperties myAutowiredProperties){
        PropertiesHolder.properties = myAutowiredProperties;
    }

    //Spring Boot command line runner autoexecuted upon component creation
    //which initializes the static properties access
    public void run(String... args) {
        PropertiesHolder.makeAvailable(myAutowiredProperties);
    }
}

MyProperties.class

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

//Example: your_properties_file_prefix.properties 
@ConfigurationProperties(prefix = "YOUR_PROPERTIES_FILE_PREFIX") 
@Component
@Data
public class MyProperties {

    private String property1;
    private String property2;
    private String property3;
}