49

I would really like to use YAML config for Spring Boot, as I find it quite readable and useful to have a single file showing what properties are active in my different profiles. Unfortunately, I'm finding that setting properties in application.yml can be rather fragile.

Things like using a tab instead of spaces will cause properties to not exist (without warnings as far as I can see), and all too often I find that my active profiles are not being set, due to some unknown issue with my YAML.

So I was wondering whether there are any hooks that would enable me to get hold of the currently active profiles and properties, so that I could log them.

Similarly, is there a way to cause start-up to fail if the application.yml contains errors? Either that or a means for me to validate the YAML myself, so that I could kill the start-up process.

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
Steve
  • 9,270
  • 5
  • 47
  • 61

8 Answers8

47

In addition to other answers: logging active properties on context refreshed event.

Java 8

package mypackage;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
@Component
public class AppContextEventListener {

    @EventListener
    public void handleContextRefreshed(ContextRefreshedEvent event) {
        printActiveProperties((ConfigurableEnvironment) event.getApplicationContext().getEnvironment());
    }

    private void printActiveProperties(ConfigurableEnvironment env) {

        System.out.println("************************* ACTIVE APP PROPERTIES ******************************");

        List<MapPropertySource> propertySources = new ArrayList<>();

        env.getPropertySources().forEach(it -> {
            if (it instanceof MapPropertySource && it.getName().contains("applicationConfig")) {
                propertySources.add((MapPropertySource) it);
            }
        });

        propertySources.stream()
                .map(propertySource -> propertySource.getSource().keySet())
                .flatMap(Collection::stream)
                .distinct()
                .sorted()
                .forEach(key -> {
                    try {
                        System.out.println(key + "=" + env.getProperty(key));
                    } catch (Exception e) {
                        log.warn("{} -> {}", key, e.getMessage());
                    }
                });
        System.out.println("******************************************************************************");
    }
}

Kotlin

package mypackage

import mu.KLogging
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.context.event.EventListener
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.MapPropertySource
import org.springframework.stereotype.Component

@Component
class AppContextEventListener {

    companion object : KLogging()

    @EventListener
    fun handleContextRefreshed(event: ContextRefreshedEvent) {
        printActiveProperties(event.applicationContext.environment as ConfigurableEnvironment)
    }

    fun printActiveProperties(env: ConfigurableEnvironment) {
        println("************************* ACTIVE APP PROPERTIES ******************************")
        env.propertySources
                .filter { it.name.contains("applicationConfig") }
                .map { it as EnumerablePropertySource<*> }
                .map { it -> it.propertyNames.toList() }
                .flatMap { it }
                .distinctBy { it }
                .sortedBy { it }
                .forEach { it ->
                    try {
                        println("$it=${env.getProperty(it)}")
                    } catch (e: Exception) {
                        logger.warn("$it -> ${e.message}")
                    }
                }
        println("******************************************************************************")
    }
}

Output like:

************************* ACTIVE APP PROPERTIES ******************************
server.port=3000
spring.application.name=my-app
...
2017-12-29 13:13:32.843  WARN 36252 --- [           main] m.AppContextEventListener        : spring.boot.admin.client.service-url -> Could not resolve placeholder 'management.address' in value "http://${management.address}:${server.port}"
...
spring.datasource.password=
spring.datasource.url=jdbc:postgresql://localhost/my_db?currentSchema=public
spring.datasource.username=db_user
...
******************************************************************************
fightlight
  • 946
  • 1
  • 11
  • 12
  • 1
    none of these solutions work for me to get anything with @ConfigurationProperties – berlinguyinca May 22 '19 at 21:57
  • @berlinguyinca Hi! What version of spring boot do you use? And why are you talking about @ConfigurationProperties? This method is for showing applied configuration properties from merged `application.yml <- application-some-profile.yml <- etc` or `same with application.properties` files at runtime. And I don't get how it is connected to @ConfigurationProperties. – fightlight May 24 '19 at 14:56
  • version 2.x and we basically want to see the default values of all defined ConfigurationPropertie annotations. – berlinguyinca May 24 '19 at 20:49
  • @berlinguyinca just tried on Spring Boot 2.1.5.RELEASE project - works as expected ¯\_(ツ)_/¯ – fightlight May 28 '19 at 16:14
  • yes the issue was, its not printing default values, only values which are explicitly set in the yaml file or set on the command line. But I wanted to print all possible configuration properties. Including defaults and not just explicitly specified ones. – berlinguyinca Jun 01 '19 at 06:01
  • 1
    It does NOT work with spring-boot 2.5.14: my app has no `PropertySource` with `applicationConfig` in its name. It works if you remove the condition on the name and you get also system properties. Note also that Java and Kotlin implementations are not strictly equivalent since the second one casts to `EnumerablePropertySource` instead of `MapPropertySource`. – Pino Oct 28 '22 at 10:23
  • Did not work for me with Spring Boot 2.7.10 / Java 17. But I posted separate [Answer](https://stackoverflow.com/a/76293145/1357094) that does work with those versions. – cellepo May 20 '23 at 00:57
  • but I think this Answer here is similar to https://www.baeldung.com/spring-boot-log-properties#1-logging-all-properties – cellepo May 20 '23 at 01:24
13

I had the same problem, and wish there was a debug flag that would tell the profile processing system to spit out some useful logging. One possible way of doing it would be to register an event listener for your application context, and print out the profiles from the environment. I haven't tried doing it this way myself, so your mileage may vary. I think maybe something like what's outlined here:

How to add a hook to the application context initialization event?

Then you'd do something like this in your listener:

System.out.println("Active profiles: " + Arrays.toString(ctxt.getEnvironment().getActiveProfiles()));

Might be worth a try. Another way you could probably do it would be to declare the Environment to be injected in the code where you need to print the profiles. I.e.:

@Component
public class SomeClass {
  @Autowired
  private Environment env;
  ...
  private void dumpProfiles() {
    // Print whatever needed from env here
  }
}
Community
  • 1
  • 1
user2337270
  • 1,183
  • 2
  • 10
  • 27
  • 1
    I adopted the approach of logging the results of `getEnvironment().getActiveProfiles()`, as part of start-up logging in my application `main` method. – Steve Dec 17 '14 at 10:33
  • 3
    it seems like spring is doing it by default nowadays: `INFO 22067 --- [ main] com.example.MainApplication : The following profiles are active: dev` – ratijas Jan 26 '20 at 15:53
12

Actuator /env service displays properties, but it doesn't displays which property value is actually active. Very often you may want to override your application properties with

  • profile-specific application properties
  • command line arguments
  • OS environment variables

Thus you will have the same property and different values in several sources.

Snippet bellow prints active application properties values on startup:

@Configuration
public class PropertiesLogger {
    private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);

    @Autowired
    private AbstractEnvironment environment;

    @PostConstruct
    public void printProperties() {

        log.info("**** APPLICATION PROPERTIES SOURCES ****");

        Set<String> properties = new TreeSet<>();
        for (PropertiesPropertySource p : findPropertiesPropertySources()) {
            log.info(p.toString());
            properties.addAll(Arrays.asList(p.getPropertyNames()));
        }

        log.info("**** APPLICATION PROPERTIES VALUES ****");
        print(properties);

    }

    private List<PropertiesPropertySource> findPropertiesPropertySources() {
        List<PropertiesPropertySource> propertiesPropertySources = new LinkedList<>();
        for (PropertySource<?> propertySource : environment.getPropertySources()) {
            if (propertySource instanceof PropertiesPropertySource) {
                propertiesPropertySources.add((PropertiesPropertySource) propertySource);
            }
        }
        return propertiesPropertySources;
    }

    private void print(Set<String> properties) {
        for (String propertyName : properties) {
            log.info("{}={}", propertyName, environment.getProperty(propertyName));
        }
    }

}
  • 2
    This printed out nothing for me. – pacoverflow Dec 19 '16 at 22:07
  • Nothing? Do you mean not even "**** APPLICATION PROPERTIES SOURCES ****" is printed on @PostConstruct? First, I woud make sure PropertiesLogger object is created at all in your application. Maybe some digging around @EnableAutoConfiguration may help. – Krzysztof Ziomek Dec 24 '16 at 00:47
  • 1
    I mean it printed out "**** APPLICATION PROPERTIES SOURCES ****" followed by nothing followed by "**** APPLICATION PROPERTIES VALUES ****" followed by nothing. – pacoverflow Dec 24 '16 at 06:56
  • 2
    Works, but one problem with this is this only works after all beans constructed. If some bean throws exception on construct, property logs are not available. – Daniel Hári Mar 01 '17 at 20:05
  • @DanielHári I've asked and answerd a question about this problem: https://stackoverflow.com/questions/48212761/how-to-log-all-active-properties-of-a-spring-boot-application-before-the-beans-i/48212783#48212783 – Ortomala Lokni Jan 11 '18 at 17:35
  • I also only got the output pacoverflow got. I found this answer to be what I wanted: https://stackoverflow.com/a/48212783/9910 – KC Baltz Jun 06 '18 at 22:03
5

If application.yml contains errors it will cause a failure on startup. I guess it depends what you mean by "error" though. Certainly it will fail if the YAML is not well formed. Also if you are setting @ConfigurationProperties that are marked as ignoreInvalidFields=true for instance, or if you set a value that cannot be converted. That's a pretty wide range of errors.

The active profiles will probably be logged on startup by the Environment implementation (but in any case it's easy for you to grab that and log it in your launcher code - the toString() of teh Environment will list the active profiles I think). Active profiles (and more) are also available in the /env endpoint if you add the Actuator.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
2

In case you want to get the active profiles before initializing the beans/application, the only way I found is registering a custom Banner in your SpringBootServletInitializer/SpringApplication (i.e. ApplicationWebXml in a JHipster application).

e.g.

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
    // set a default to use when no profile is configured.
    DefaultProfileUtil.addDefaultProfile(builder.application());
    return builder.sources(MyApp.class).banner(this::printBanner);
}

/** Custom 'banner' to obtain early access to the Spring configuration to validate and debug it. */
private void printBanner(Environment env, Class<?> sourceClass, PrintStream out)
{
    if (env.getProperty("spring.datasource.url") == null)
    {
        throw new RuntimeException(
            "'spring.datasource.url' is not configured! Check your configuration files and the value of 'spring.profiles.active' in your launcher.");
    }
    ...
}

  • Edited my response and added the sample code. In case you are wondering, for my app I don't enumerate all properties there, just check the critical ones. But the Environment you receive in the Banner is a ConfigurableEnvironment, so you can iterate over getPropertySources() and enumerate from the sources that implement EnumerablePropertySource. – Flávio Etrusco Jun 18 '18 at 21:46
0

If you want to log few active config values from application.yml file during the startup of your spring boot application. You have to use ApplicationReadyListener Class from spring framework like this:

public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {

@Value("${key:defaultValue}")
private String keyValue;

@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
  log.info("Active config value for key is: {}", keyValue);
}
AnjuR
  • 91
  • 3
0

If you want to print configuration properties

Java 17, Spring Boot 2.7.10

(tested with non-YAML)

Just add this class anywhere in your Spring Boot project's src:

package your.package;

import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class AppContextEventListener {

    @EventListener
    public void printProperties(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("************************* ACTIVE PROPERTIES *************************");

        ((ConfigurableEnvironment) contextRefreshedEvent.getApplicationContext().getEnvironment())
            .getPropertySources()
            .stream()
            .filter(ps -> ps instanceof OriginTrackedMapPropertySource)
            // Convert each PropertySource to its properties Set
            .map(ps -> ((OriginTrackedMapPropertySource) ps).getSource().entrySet())
            .flatMap(Collection::stream)
            // Print properties within each Set
            .forEach(property -> System.out.println(property.getKey() + "=" + property.getValue()));

        System.out.println("*********************************************************************");
    }

}

prints out something like:

...
************************* ACTIVE PROPERTIES ***********************
property - your.property.0: yourValue0
property - your.property.1: yourValue1
property - your.property.2: yourValue2
*******************************************************************
Started YourApp in n.nnn seconds (JVM running for m.mmm)
...
cellepo
  • 4,001
  • 2
  • 38
  • 57
0

If you are using spring-boot-starter-actuator, then you can access the actuator/configprops endpoint to show all the loaded properties, e.g. http://localhost:8080/actuator/configprops

More detail here: https://www.baeldung.com/spring-boot-actuators

manyways
  • 4,386
  • 2
  • 23
  • 19