Hot questions for Spring Properties

Hot questions for Spring Properties

Top 10 Java Open Source / Spring / Spring Properties

Question:

I want to have a list of values in a .properties file, ie:

my.list.of.strings=ABC,CDE,EFG

And to load it in my class directly, ie:

@Value("${my.list.of.strings}")
private List<String> myList;

As I understand, an alternative of doing this is to have it in the spring config file, and load it as a bean reference (correct me if I'm wrong), ie

<bean name="list">
 <list>
  <value>ABC</value>
  <value>CDE</value>
  <value>EFG</value>
 </list>
</bean>

But is there any way of doing this? using a .properties file? ps: I would like to do this with out any custom code if possible.


Answer:

Using Spring EL:

@Value("#{'${my.list.of.strings}'.split(',')}") 
private List<String> myList;

Assuming your properties file is loaded correctly with the following:

my.list.of.strings=ABC,CDE,EFG

Question:

I am working on a Spring Boot application. I need to parse an XML file (countries.xml) on start. The problem is that I do not understand where to put it so that I could access it. My folders structure is

ProjectDirectory/src/main/java
ProjectDirectory/src/main/resources/countries.xml

My first idea was to put it in src/main/resources, but when I try to create File (countries.xml) I get a NPE and the stacktrace shows that my file is looked in the ProjectDirectory (so src/main/resources/ is not added). I tried to create File (resources/countries.xml) and the path would look like ProjectDirectory/resources/countries.xml (so again src/main is not added).

I tried adding this with no result

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    super.addResourceHandlers(registry);
}

I know that I can add src/main/ manually, but I want to understand why is it not working as it has to. I also tried examples with ResourceLoader - with the same no result.

Could anyone suggest what the problem is?

UPDATE: Just for future references - after building the project, I encountered problem with accessing file, so I changed File to InputStream

InputStream is = new ClassPathResource("countries.xml").getInputStream();

Answer:

Just use Spring type ClassPathResource.

File file = new ClassPathResource("countries.xml").getFile();

As long as this file is somewhere on classpath Spring will find it. This can be src/main/resources during development and testing. In production, it can be current running directory.

EDIT: This approach doesn't work if file is in fat JAR. In such case you need to use:

InputStream is = new ClassPathResource("countries.xml").getInputStream();

Question:

I've had this working in some other project before, I am just re-doing the same thing but for some reason it's not working. The Spring @Value is not reading from property file, but instead it's taking the value literally

AppConfig.java

@Component
public class AppConfig
{
    @Value("${key.value1}")
    private String value;

    public String getValue()
    {
        return value;
    }
}

applicationContext.xml:

<context:component-scan
    base-package="com.test.config" />
<context:annotation-config />

<bean id="appConfigProperties"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:appconfig.properties" />
</bean>

appconfig.properties

key.value1=test value 1

In my controller, where I have:

@Autowired
private AppConfig appConfig;

The application starts just fine, but when I do

appConfig.getValue()

it returns

${key.value1}

It doesn't resolve to the value inside the properties file.

Thoughts?


Answer:

I also found the reason @value was not working is, @value requires PropertySourcesPlaceholderConfigurer instead of a PropertyPlaceholderConfigurer. i did the same changes and it worked for me, i am using spring 4.0.3 release. I configured this using below code in my configuration file -

@Bean 
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

Question:

I have the following property annotated with @Value. I have a default value defined using the default separator of ':"

@Value("${prop.url:http://myurl.com}")

Is there a way to escape the ':' in http://myurl.com or do I have to define a different separator value in my configuration.


Answer:

Update: For spring 4.2 and higher, no single quotes are needed. Spring will see the first colon as special, and use all the rest as a single string value.

For spring 4.2 and higher,

@Value("${prop.url:http://myurl.com}")

For the previous versions, I believe single quotes will do the trick:

@Value("${prop.url:'http://myurl.com'}")

Question:

I want to use the @Value annotation to inject a Double property such as:

@Service
public class MyService {

    @Value("${item.priceFactor}")
    private Double priceFactor = 0.1;

// ...

and using Spring property placeholder (Properties files):

item.priceFactor=0.1

I get Exception:

org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Double'; nested exception is java.lang.NumberFormatException: For input string: "${item.priceFactor}"

Is there a way to use a Double value coming from a properties file?


Answer:

Try changing the following line

@Value("${item.priceFactor}")

to

@Value("#{new Double('${item.priceFactor}')}")

Question:

Enum

public enum Property {
    A,
    AB,
    ABC;
}

Field

@Value("${custom.property}")
protected Property property;

application.properties (lower case)

custom.property=abc

When I'm running application I have an error:

Cannot convert value of type [java.lang.String] to required type [com.xxx.Property]: no matching editors or conversion strategy found.

Whereas (upper case):

custom.property=ABC

Works fine.

Is there a way to bind the value case insensitive? Like ABC, Abc, AbC, abc any pattern should work.

NOTE: I saw this question - Spring 3.0 MVC binding Enums Case Sensitive but in my case I have over 10 enums/values (and expect to have more) classes and to implement 10 different custom property binders would be painful, I need some generic solution.


Answer:

@Value and @ConfigurationProperties features do not match. I couldn't stress enough how @ConfigurationProperties is superior.

First, you get to design your configuration in a simple POJO that you can inject wherever you want (rather than having expressions in annotation that you can easily break with a typo). Second, the meta-data support means that you can very easily get auto-completion in your IDE for your own keys.

And finally, the relaxed binding described in the doc only applies to @ConfigurationProperties. @Value is a Spring Framework feature and is unaware of relaxed binding. We intend to make that more clear in the doc.

TL;DR abc works with @ConfigurationProperties but won't with @Value.

Question:

Is there a way we can lookup file resources using relative path in application.properties file in Spring boot application as specified below

spring.datasource.url=jdbc:hsqldb:file:${project.basedir}/db/init

Answer:

@membersound answer is just breaking up the hardcoded path in 2 parts, not dynamically resolving the property. I can tell you how to achieve what you're looking for, but you need to understand is that there is NO project.basedir when you're running the application as a jar or war. Outside the local workspace, the source code structure doesn't exist.

If you still want to do this for testing, that's feasible and what you need is to manipulate the PropertySources. Your simplest option is as follows:

Define an ApplicationContextInitializer, and set the property there. Something like the following:

    public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext appCtx) {
        try {
            // should be /<path-to-projectBasedir>/build/classes/main/
            File pwd = new File(getClass().getResource("/").toURI());
            String projectDir = pwd.getParentFile().getParentFile().getParent();
            String conf = new File(projectDir, "db/init").getAbsolutePath();
            Map<String, Object> props = new HashMap<>();
            props.put("spring.datasource.url", conf);
            MapPropertySource mapPropertySource = new MapPropertySource("db-props", props);
            appCtx.getEnvironment().getPropertySources().addFirst(mapPropertySource);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }}

Looks like you're using Boot, so you can just declare context.initializer.classes=com.example.MyApplicationContextInitializer in your application.properties and Boot will run this class at startup.

Words of caution again:

  1. This will not work outside the local workspace as it depends on the source code structure.

  2. I've assumed a Gradle project structure here /build/classes/main. If necessary, adjust according to your build tool.

  3. If MyApplicationContextInitializer is in the src/test/java, pwd will be <projectBasedir>/build/classes/test/, not <projectBasedir>/build/classes/main/.

Question:

I have a application.properites file with the following:

xxx.xxx = sandbox
xxx.sandbox = 123
xxx.production = 456

I would like to map to a string value 123 in case xxx.xxx == sandbox and 456 in case xxx.xxx == production

...
public class temp { 

 @Value("${??????}")
    private String token;
}

is it possible to fill in a condition incited of the ?????? that will map the token to 123 or 456 according to xxx.xxx ?


Answer:

A simple way in case someone will hit this question:

@Value("#{'${xxx.xxx}'=='sandbox' ? '${xxx.sandbox}' : '${xxx.production}'}")

I just think it is much easier that start working with profiles.

Question:

I have a requirement of accessing application.properties file outside the project location. I am able to achieve the same using following :

@Component
@PropertySources({
        @PropertySource(value = "file:${user.home}/file/path/application.properties", ignoreResourceNotFound = false) })
public class PropConfig implements InitializingBean {

Now, I want to achieve the same using active profile. If dev profile is active, I need to fetch application-dev.properties, if stage profile is active, I want to fetch application-stage.properties and so on.

I am using Windows platform and JAVA 8 with Spring Boot 1.5.x

I tried setting the active profile in application.properties file. But it doesn't work

spring.profiles.active=dev

Answer:

Solution for Spring Boot 1.5.X

You can add the folder as a custom config location by running your app with the following JVM argument:

-Dspring.config.location=file:${user.home}/file/path/

With this JVM argument configured, all application-{profile}.properties files within this folder will be automatically resolved.

( Alternatively, if you prefer to use environment variables instead of JVM arguments, you can do the same thing by setting the SPRING_CONFIG_LOCATION environment variable, for example by using following command in the linux terminal: export SPRING_CONFIG_LOCATION=file:${user.home}/file/path/ )

Now, if you have a file application-dev.properties in your custom config folder, it should be enough to activate the profile in your default application.properties file by adding:

spring.profiles.active=dev

Finally, the @PropertySources annotation is redundant and you can remove it:

@Component
public class PropConfig implements InitializingBean {

Reference: https://docs.spring.io/spring-boot/docs/1.5.0.RELEASE/reference/html/boot-features-external-config.html


Solution for Spring Boot 2.X

The approach is mainly the same as for Spring Boot 1.5.X but with a slight difference.

In Spring Boot 2.X the behavior of the spring.config.location argument is slightly different than in earlier versions. The difference is that in Spring Boot 2.X the spring.config.location argument overrides the default config locations:

When custom config locations are configured by using spring.config.location, they replace the default locations. (Source: Spring Boot Documentation)

Since setting this argument to your custom config folder would override the default locations (I suppose that losing the config files on the default config locations is not the desired behavior), it is better to use the new spring.config.additional-location argument which doesn't override but only extend the default locations:

-Dspring.config.additional-location=file:${user.home}/file/path/

( Alternatively, you can use the SPRING_CONFIG_ADDITIONAL-LOCATION environment variable if you prefer to use environment variables instead of JVM arguments )

Reference: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Question:

I have property file application-dev.yml with content:

spring.profiles: dev
config.some.value:
- ELEMENT1
- ELEMENT2

and another application-staging.yml with content:

spring.profiles: staging
config.some.value:
- ELEMENT1
- ELEMENT2
- ELEMENT3

so I basically do not know size of list. When I reference this list in main application.yml like this:

some.value: ${config.some.value}

I get Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'value'. How to reference it correctly?


Answer:

Solution

One way would be to use comma-separated lists in your profiles:

  • application-dev.yml
spring.profiles: dev
config.some.value: ELEMENT1,ELEMENT2
  • application-staging.yml
spring.profiles: staging
config.some.value: ELEMENT1,ELEMENT2,ELEMENT3

Then you should be able to access it in application.yml

some.value: ${config.some.value}

This solution doesn't require knowing list size upfront.

Explanation

The reason why this is working is described here. Specifically:

YAML lists are represented as comma-separated values (useful for simple String values) and also as property keys with [index] dereferencers, for example this YAML:
servers:
    - dev.bar.com
    - foo.bar.com
Would be transformed into these properties:
servers=dev.bar.com,foo.bar.com
servers[0]=dev.bar.com
servers[1]=foo.bar.com

In particular this means, that if you specify comma-separated list of strings in application.yml and define List<String> as value in @ConfigurationProperties, spring configuration properties binder will convert that comma-separated list of string to List<Strings>.

Question:

I have created a Spring Boot app which uses a legacy library. This legacy library defines a number of Spring Beans in XML. One of which take in a property value as a constructor argument:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="myBean" class="com.em.MyBean">
        <constructor-arg name="url" value="${my.url}"/>
    </bean>
</beans>

In my Spring Boot app, I have an application.properties which defines this property as follows:

my.url=http://localhost:8080

I use the Maven Spring Boot plugin to run my app locally as follows:

mvn spring-boot:run

And the property value is injected into the bean as expected.

If I try and override the my.url property on the command line like this:

mvn spring-boot:run -Dmy.url=http://www.override.net

The overriden value is not used and instead, the value inside application.properties is used.

According to the Spring Boot docs, values from the command line should be picked up as the first priority: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html. That does not appear to be the case here because if I remove the property from application.properties then the value passed in on the command line is used so it is not a case of the command line value being ignored altogether. It seems like the application.properties value is overriding the command line value.

Does anyone have any ideas as to what is going on?


Answer:

Using -D sets a system property. Spring Boot can consume configuration from System properties so, generally speaking, it'll work. However, it won't work if spring-boot:run is forking a separate JVM for your application as the System property will be set on the wrong JVM. As it is not working, I would guess that is what is happening.

You can use -Drun.arguments to pass arguments to the application that's being run, irrespective of whether it's run in a forked JVM. The arguments should be a comma-separated list each prefixed with --. For example, to set my.url:

mvn spring-boot:run -Drun.arguments=--my.url=http://www.override.net

The other possible cause of this problem is that your main method isn't passing the arguments that it receives into the SpringApplication that it creates. You should also check that your main method looks similar to this:

public static void main(String[] args) throws Exception {
    SpringApplication.run(YourApplication.class, args);
}

Note that args is being passed into the call to SpringApplication.run.