111

I am using annotations to configure my spring environment like this:

@Configuration
...
@PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;
}

This leads to my properties from default.properties being part of the Environment. I want to use the @PropertySource mechanism here, because it already provides the possibility to overload properties through several fallback layers and different dynamic locations, based on the environment settings (e.g. config_dir location). I just stripped the fallback to make the example easier.

However, my problem now is that I want to configure for example my datasource properties in default.properties. You can pass the settings to the datasource without knowing in detail what settings the datasource expects using

Properties p = ...
datasource.setProperties(p);

However, the problem is, the Environment object is neither a Properties object nor a Map nor anything comparable. From my point of view it is simply not possible to access all values of the environment, because there is no keySet or iterator method or anything comparable.

Properties p <=== Environment env?

Am I missing something? Is it possible to access all entries of the Environment object somehow? If yes, I could map the entries to a Map or Properties object, I could even filter or map them by prefix - create subsets as a standard java Map ... This is what I would like to do. Any suggestions?

codeforester
  • 39,467
  • 16
  • 112
  • 140
RoK
  • 1,862
  • 3
  • 14
  • 17

13 Answers13

102

You need something like this, maybe it can be improved. This is a first attempt:

...
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
...

@Configuration
...
@org.springframework.context.annotation.PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;

    public void someMethod() {
        ...
        Map<String, Object> map = new HashMap();
        for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
            PropertySource propertySource = (PropertySource) it.next();
            if (propertySource instanceof MapPropertySource) {
                map.putAll(((MapPropertySource) propertySource).getSource());
            }
        }
        ...
    }
...

Basically, everything from the Environment that's a MapPropertySource (and there are quite a lot of implementations) can be accessed as a Map of properties.

JimHawkins
  • 4,843
  • 8
  • 35
  • 55
Andrei Stefan
  • 51,654
  • 6
  • 98
  • 89
  • Thanks for sharing this approach. I concider this as a bit "dirty" but it is probably the only way to go here. Another approach a colleague showed me would be to put a property into the configuration using a fixed key that holds a list with all property keys. You could then read the properties into a Map/Properties object based on the keylist. That would at least prevent the casts ... – RoK May 08 '14 at 23:08
  • 22
    Note for Spring boot ... that getPropertySources() returns the PropertySource in the precedence order so you effectively need to reverse that in the cases where property values are overwritten – Rob Bygrave Oct 27 '15 at 09:40
  • 2
    As @RobBygrave mentioned the order might be different, but instead of reverting the order(since you can deploy spring boot to container as war or this behaviour can change in future) I would just collect all keys and then use `applicationContext.getEnvironment().getProperty(key)` to resolve them – potato Mar 04 '16 at 11:02
  • @potato That's a good idea, and I tried that. The only potential problem is that you run into evaluation issues with placeholders, like in this question here: http://stackoverflow.com/questions/34584498/how-to-get-raw-value-of-property-containing-placeholders – bischoje Mar 15 '16 at 16:46
  • I was able to work through the placeholder issue referenced in my comment above by catching any IllegalArgumentException: `Map map = new HashMap();` `for(String key : keys) {` `try {` `String value = environment.getProperty(key);` `map.put(key, value);` `}` `catch (IllegalArgumentException e) {` `// This key includes an unresolveable placeholder, and will not be part of the map that we output` `}` `}` – bischoje Mar 15 '16 at 17:33
  • 1
    Thank you!.. I was looking for a spring alternative to use in place of org.apache.ibatis.io.Resources.getResourceAsProperties("Filepath") This solution worked very well for me. – so-random-dude Oct 24 '16 at 07:01
  • I have done that but still there are some @value parameters whose key are not present in that map but they are getting values which are not defined any where in the project. I am pretty new to spring so pardon me if am wrong about the terminology. – pannu Jun 02 '17 at 09:19
  • While this idea works for the majority of property sources, it does not work for the command line arguments property source (`SimpleCommandLinePropertySource`). There are casting issues involved. The only way I have gotten this to work is through reflection. – Chad Van De Hey May 23 '18 at 16:34
100

This is an old question, but the accepted answer has a serious flaw. If the Spring Environment object contains any overriding values (as described in Externalized Configuration), there is no guarantee that the map of property values it produces will match those returned from the Environment object. I found that simply iterating through the PropertySources of the Environment did not, in fact, give any overriding values. Instead it produced the original value, the one that should have been overridden.

Here is a better solution. This uses the EnumerablePropertySources of the Environment to iterate through the known property names, but then reads the actual value out of the real Spring environment. This guarantees that the value is the one actually resolved by Spring, including any overriding values.

Properties props = new Properties();
MutablePropertySources propSrcs = ((AbstractEnvironment) springEnv).getPropertySources();
StreamSupport.stream(propSrcs.spliterator(), false)
        .filter(ps -> ps instanceof EnumerablePropertySource)
        .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames())
        .flatMap(Arrays::<String>stream)
        .forEach(propName -> props.setProperty(propName, springEnv.getProperty(propName)));
pedorro
  • 3,079
  • 1
  • 24
  • 24
  • 2
    It's worth noting that as of Spring 4.1.2, this solution (unlike the other answers) doesn't need to be updated to explicitly deal with CompositePropertySource, as CompositePropertySource extends EnumerablePropertySource and therefore getPropertyNames will return the set of all property names in the composite source. – M. Justin Mar 22 '17 at 19:17
  • 5
    You could also collect the properties using the built-in `collect` method on the stream instead of doing a `forEach`: `.distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty))`. If you needed to collect it into a Properties instead of a Map you could use the four-argument version of `collect`. – M. Justin Mar 22 '17 at 20:04
  • 3
    What's `springEnv`? Where does it come from? Does it differ from the `env` of the accepted solution? – sebnukem Jan 25 '18 at 20:43
  • 4
    @sebnukem Good point. `springEnv` is the `env` object of the original question & accepted solution. I suppose I should have kept the name the same. – pedorro Jan 25 '18 at 20:59
  • 5
    You could use `ConfigurableEnvironment ` and not have to do the cast. – Abhijit Sarkar Mar 27 '18 at 23:52
  • 1
    @bruno why do you insist on changing my answer? I don't like your version. I don't want it associated with my name. Add your own answer if you like yours so much – pedorro Feb 25 '21 at 01:09
21

I had the requirement to retrieve all properties whose key starts with a distinct prefix (e.g. all properties starting with "log4j.appender.") and wrote following Code (using streams and lamdas of Java 8).

public static Map<String,Object> getPropertiesStartingWith( ConfigurableEnvironment aEnv,
                                                            String aKeyPrefix )
{
    Map<String,Object> result = new HashMap<>();

    Map<String,Object> map = getAllProperties( aEnv );

    for (Entry<String, Object> entry : map.entrySet())
    {
        String key = entry.getKey();

        if ( key.startsWith( aKeyPrefix ) )
        {
            result.put( key, entry.getValue() );
        }
    }

    return result;
}

public static Map<String,Object> getAllProperties( ConfigurableEnvironment aEnv )
{
    Map<String,Object> result = new HashMap<>();
    aEnv.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
    return result;
}

public static Map<String,Object> getAllProperties( PropertySource<?> aPropSource )
{
    Map<String,Object> result = new HashMap<>();

    if ( aPropSource instanceof CompositePropertySource)
    {
        CompositePropertySource cps = (CompositePropertySource) aPropSource;
        cps.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
        return result;
    }

    if ( aPropSource instanceof EnumerablePropertySource<?> )
    {
        EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource;
        Arrays.asList( ps.getPropertyNames() ).forEach( key -> result.put( key, ps.getProperty( key ) ) );
        return result;
    }

    // note: Most descendants of PropertySource are EnumerablePropertySource. There are some
    // few others like JndiPropertySource or StubPropertySource
    myLog.debug( "Given PropertySource is instanceof " + aPropSource.getClass().getName()
                 + " and cannot be iterated" );

    return result;

}

private static void addAll( Map<String, Object> aBase, Map<String, Object> aToBeAdded )
{
    for (Entry<String, Object> entry : aToBeAdded.entrySet())
    {
        if ( aBase.containsKey( entry.getKey() ) )
        {
            continue;
        }

        aBase.put( entry.getKey(), entry.getValue() );
    }
}

Note that the starting point is the ConfigurableEnvironment which is able to return the embedded PropertySources (the ConfigurableEnvironment is a direct descendant of Environment). You can autowire it by:

@Autowired
private ConfigurableEnvironment  myEnv;

If you not using very special kinds of property sources (like JndiPropertySource, which is usually not used in spring autoconfiguration) you can retrieve all properties held in the environment.

The implementation relies on the iteration order which spring itself provides and takes the first found property, all later found properties with the same name are discarded. This should ensure the same behaviour as if the environment were asked directly for a property (returning the first found one).

Note also that the returned properties are not yet resolved if they contain aliases with the ${...} operator. If you want to have a particular key resolved you have to ask the Environment directly again:

myEnv.getProperty( key );
Heri
  • 4,368
  • 1
  • 31
  • 51
  • 2
    Why not just discover all the keys this way and then use environment.getProperty to force appropriate value resolution? Would want to ensure that environment overrides are respected, e.g. application-dev.properties overrides a default value in application.properties and as you mentioned placeholder eval. – GameSalutes Jan 09 '17 at 21:03
  • That's what I pointed out in the last paragraph. Using env.getProperty ensures the original behaviour of Spring. – Heri Jan 10 '17 at 07:25
  • How do you unit test this? I always get a `NullPointerException` in my unit tests when it tries to get the `@Autowired` instance of `ConfigurationEnvironment`. – ArtOfWarfare Dec 23 '19 at 16:56
  • Are you sure you run your test as spring application? – Heri Dec 23 '19 at 17:21
  • I do it like this: – Heri Dec 23 '19 at 17:22
  • String[] configFiles = { "appCtxtCommon.xml" }; ApplicationContext myAppContext = new ClassPathXmlApplicationContext( configFiles ); assertNotNull(myAppContext); myEnv = JUnitHelper.assertNotNullAndCast(myAppContext.getEnvironment(), ConfigurableEnvironment.class); – Heri Dec 23 '19 at 17:22
  • And in others just with Autowired:@Autowired private ConfigurableEnvironment myEnv; – Heri Dec 23 '19 at 17:23
19

The original question hinted that it would be nice to be able to filter all the properties based on a prefix. I have just confirmed that this works as of Spring Boot 2.1.1.RELEASE, for Properties or Map<String,String>. I'm sure it's worked for while now. Interestingly, it does not work without the prefix = qualification, i.e. I do not know how to get the entire environment loaded into a map. As I said, this might actually be what OP wanted to begin with. The prefix and the following '.' will be stripped off, which might or might not be what one wants:

@ConfigurationProperties(prefix = "abc")
@Bean
public Properties getAsProperties() {
    return new Properties();
}

@Bean
public MyService createService() {
    Properties properties = getAsProperties();
    return new MyService(properties);
}

Postscript: It is indeed possible, and shamefully easy, to get the entire environment. I don't know how this escaped me:

@ConfigurationProperties
@Bean
public Properties getProperties() {
    return new Properties();
}
AbuNassar
  • 1,128
  • 12
  • 12
  • 1
    also, Properties like a.b.c=x get nested into {b={c=x}} – weberjn Mar 05 '19 at 17:18
  • No portion of this worked - `getAsProperties()` always returns an empty `Properties` instance, and trying it without a prefix specified doesn't even allow it to compile. This is with Spring Boot 2.1.6.RELEASE – ArtOfWarfare Dec 23 '19 at 16:55
  • 2
    I'm not writing Java at work, but I whipped this up pretty quickly: https://github.com/AbuCarlo/SpringPropertiesBean. It's possible that it won't work if you somehow circumvent Spring's startup sequence (i.e. the "properties" bean is never populated). This is for Java 8, Spring 2.2.6. – AbuNassar Apr 01 '20 at 14:36
  • Abu's code worked fine in 2018. For whatever reason, Spring Boot's `@ConfigurationProperties` annotation now requires a `prefix` – MarkHu May 10 '22 at 18:34
  • This works magically as advertised for me with Spring Boot 2.7.2 -- thanks @AbuNassar! I'm using just `@ConfigurationProperties` (without a prefix) and it compiles fine. Intellij shows a warning about a prefix being required, but not the java compiler. – Chris Toomey Aug 10 '22 at 00:14
8

As this Spring's Jira ticket, it is an intentional design. But the following code works for me.

public static Map<String, Object> getAllKnownProperties(Environment env) {
    Map<String, Object> rtn = new HashMap<>();
    if (env instanceof ConfigurableEnvironment) {
        for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) {
            if (propertySource instanceof EnumerablePropertySource) {
                for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
                    rtn.put(key, propertySource.getProperty(key));
                }
            }
        }
    }
    return rtn;
}
jasonleakey
  • 1,876
  • 1
  • 12
  • 5
5

Spring won't allow to decouple via java.util.Properties from Spring Environment.

But Properties.load() still works in a Spring boot application:

Properties p = new Properties();
try (InputStream is = getClass().getResourceAsStream("/my.properties")) {
    p.load(is);
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
weberjn
  • 1,840
  • 20
  • 24
1

The other answers have pointed out the solution for the majority of cases involving PropertySources, but none have mentioned that certain property sources are unable to be casted into useful types.

One such example is the property source for command line arguments. The class that is used is SimpleCommandLinePropertySource. This private class is returned by a public method, thus making it extremely tricky to access the data inside the object. I had to use reflection in order to read the data and eventually replace the property source.

If anyone out there has a better solution, I would really like to see it; however, this is the only hack I have gotten to work.

Chad Van De Hey
  • 2,716
  • 3
  • 29
  • 46
1

Working with Spring Boot 2, I needed to do something similar. Most of the answers above work fine, just beware that at various phases in the app lifecycles the results will be different.

For example, after a ApplicationEnvironmentPreparedEvent any properties inside application.properties are not present. However, after a ApplicationPreparedEvent event they are.

Mike
  • 4,722
  • 1
  • 27
  • 40
1

For Spring Boot, the accepted answer will overwrite duplicate properties with lower priority ones. This solution will collect the properties into a SortedMap and take only the highest priority duplicate properties.

final SortedMap<String, String> sortedMap = new TreeMap<>();
for (final PropertySource<?> propertySource : env.getPropertySources()) {
    if (!(propertySource instanceof EnumerablePropertySource))
        continue;
    for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames())
        sortedMap.computeIfAbsent(name, propertySource::getProperty);
}
Jeff Brower
  • 594
  • 3
  • 13
1

I though I'd add one more way. In my case I supply this to com.hazelcast.config.XmlConfigBuilder which only needs java.util.Properties to resolve some properties inside the Hazelcast XML configuration file, i.e. it only calls getProperty(String) method. So, this allowed me to do what I needed:

@RequiredArgsConstructor
public class SpringReadOnlyProperties extends Properties {

  private final org.springframework.core.env.Environment delegate;

  @Override
  public String getProperty(String key) {
    return delegate.getProperty(key);
  }

  @Override
  public String getProperty(String key, String defaultValue) {
    return delegate.getProperty(key, defaultValue);
  }

  @Override
  public synchronized String toString() {
    return getClass().getName() + "{" + delegate + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    if (!super.equals(o)) return false;
    SpringReadOnlyProperties that = (SpringReadOnlyProperties) o;
    return delegate.equals(that.delegate);
  }

  @Override
  public int hashCode() {
    return Objects.hash(super.hashCode(), delegate);
  }

  private void throwException() {
    throw new RuntimeException("This method is not supported");
  }

  //all methods below throw the exception

  * override all methods *
}

P.S. I ended up not using this specifically for Hazelcast because it only resolves properties for XML file but not at runtime. Since I also use Spring, I decided to go with a custom org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames. This resolves properties for both situations, at least if you use properties in cache names.

Sam
  • 1,832
  • 19
  • 35
1

To get ONLY properties, defined in my hibernate.properteies file:

@PropertySource(SomeClass.HIBERNATE_PROPERTIES)
public class SomeClass {

  public static final String HIBERNATE_PROPERTIES = "hibernate.properties";

  @Autowired
  private Environment env;

  public void someMethod() {
    final Properties hibProps = asProperties(HIBERNATE_PROPERTIES);
  }

  private Properties asProperties(String fileName) {
    return StreamSupport.stream(
      ((AbstractEnvironment) env).getPropertySources().spliterator(), false)
      .filter(ps -> ps instanceof ResourcePropertySource)
      .map(ps -> (ResourcePropertySource) ps)
      .filter(rps -> rps.getName().contains(fileName))
      .collect(
        Properties::new,
        (props, rps) -> props.putAll(rps.getSource()),
        Properties::putAll);
  }
}
Alexandr
  • 9,213
  • 12
  • 62
  • 102
0

A little helper to analyze the sources of a property, which sometimes drive me crazy . I used this discussion to write SpringConfigurableEnvironment.java on github.

It could be used in a test:

@SpringBootTest
public class SpringConfigurableEnvironmentTest {
    @Autowired
    private ConfigurableEnvironment springEnv;

    @Test
    public void testProperties() {
        SpringConfigurableEnvironment properties = new SpringConfigurableEnvironment(springEnv);
        SpringConfigurableEnvironment.PropertyInfo info = properties.get("profile.env");
        assertEquals("default", properties.get(info.getValue());
        assertEquals(
           "Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'", 
           info.getSourceList.get(0));
    }
}
0

All answers above have pretty much covers everything, but be aware of overridden values from environment variables. They may have different key values.

For example, if a user override my.property[1].value using environment variable MY_PROPERTY[1]_VALUE, iterating through EnumerablePropertySources.getPropertyNames() would give you both my.property[1].value and MY_PROPERTY[1]_VALUE key values.

What even worse is that if my.property[1].value is not defined in applications.conf (or applications.yml), a MY_PROPERTY[1]_VALUE in environment variables would not give you my.property[1].value but only MY_PROPERTY[1]_VALUE key value from EnumerablePropertySources.getPropertyNames().

So it is developers' job to cover the those properties from environment variables. Unfortunately, there is no one-on-one mapping between environment variables schema vs the normal schema, see the source code of SystemEnvironmentPropertySource. For example, MY_PROPERTY[1]_VALUE could be either my.property[1].value or my-property[1].value

Charles Chen
  • 21
  • 1
  • 3