62

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
  • 919
  • 1
  • 14
  • 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

7 Answers7

86

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
  • 40,216
  • 10
  • 104
  • 131
  • 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
32

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
  • 12,336
  • 15
  • 73
  • 115
  • 3
    So there is no easy way to initialize multiple static variables with values from the properties file? – dannemp Jul 19 '17 at 13:50
  • 1
    @dannemp no. but its not very complicated! – Patrick Jul 19 '17 at 13:51
  • 4
    Updating static non-final field from non-static method is asking for a problem. – Szymon Stepniak Jul 19 '17 at 14:20
  • SVN_URL doesn't have to match above in the naming of variable. Inside @Value you can put any legit property and it'll pass on to snvUrl parameter. – Smart Coder Jul 22 '21 at 19:12
  • Watch out !!! Make sure that the setter method is not static.. if SETTER METHOD is generated using IDE then static will be added to the setter method. Make sure it is removed else solution will not work. – Aditya Rewari Aug 08 '22 at 06:25
9

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
  • 103
  • 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
  • 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
  • 1,435
  • 19
  • 19
  • it's a duplicate of already existing answer from Patrick: https://stackoverflow.com/a/45192557/1873995 – Kamil Jan 17 '22 at 14:52
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;
}
1

At least one more simple solution with configuration file:

@Configuration
public class YourStaticPropertyConfiuration {
  public static String PROPERTY_NAME;

  @Value("${propertyName}")
  public void setProperty(final String propertyName) {
    PROPERTY_NAME = propertyName;
  }
}

Use PROPERTY_NAME anywhere as static variable

Jackkobec
  • 5,889
  • 34
  • 34
0

For all those who, for whatever reason, want to provide setting properties imported from files as static properties, here is a solution that is as simple and safe as possible.

The Problem:

Spring Boot unfortunately doesn't provide a simple way to import properties from a file and bind them as static properties to a class. One possible solution to achieve that anyway would be to set the static properties using `@Value` annotations like this:
public class GlobalProperties {

    public static String NAME;

    @Value("${configs.boss.name}")
    public void setName(String name) {
        NAME = name;
    }
}

However, this approach would mean that the static properties cannot be declared as final.
And we certainly don't want that.

The Solution:

application.yml:

configs:
  boss:
    name: "Leeloo Multipass"

ConfigProperties.java:


@Validated
@ConfigurationProperties(prefix = "configs")
public record ConfigProperties(@NotNull Boss boss) {

    public static final String BOSS_NAME = BOSS.NAME;

    private static class Boss {

        private static String NAME;

        public Boss(String name) {
            NAME = name;
        }
    }
}

The solution is based on the assumption that Spring Boot builds the configuration objects first, and the properties' configuration objects first of all. So at the time Spring Boot adds the prepared objects as Bean to the context, the nested classes are already setup and the static properties initialization of ConfigProperties can access the static properties of the nested classes (which still are not final, but also not accessible from outside). This way it is possible to provide all properties declared as static final. Unless Spring Boot doesn't decide to change its internal initialization process, everything is cream & cookie.

This approach was tested with Spring Boot 3 and Java 17. It is of course possible to provide the properties additionally via the configs-bean. For that, the properties of the nested classes must be explicitly specified and their corresponding getters must be implemented. In this case, some simplification can be achieved by using records instead of classes.

Slevin
  • 617
  • 1
  • 7
  • 17