Mise à jour du 23/04/2026 : description plus précise de la résolution des propriétés dans Spring

Illustration de l’affichage des propriétés avec leur origine

Spring Boot possède une fonctionnalité d’externalisation de la configuration extrêmement riche permettant de configurer les applications à partir de sources très variées (fichiers dans le classpaths, fichiers externes, variables d’environnement, propriétés systèmes, arguments de la ligne de commande, Servlet context …). La multiplicité des sources peut parfois entraîner de la confusion et aboutir à une mauvaise configuration de l’application. D’autant plus que la propriété mal configurée peut être difficile à identifier si elle empêche l’application de démarrer et d’afficher suffisament de logs pour diagnostiquer le problème.

Ce billet présente une solution à ce problème : l’affichage précoce des propriétés utilisées dans l’application avec la valeur réellement prise en compte par Spring Boot et une indication sur la source dont provient de cette valeur. Afin de présenter une solution pour l’affichage des propriétés disponibles dans l’application et leur origine, nous verrons :

  1. Comment Spring Boot gère les propriétés et résout leurs valeurs ?
  2. Comment connaître les clés de propriété disponibles dans une application Spring ?
  3. Comment connaître l’origine de la valeur d’une propriété avec l’API Origin de Spring Boot ?
  4. Comment afficher les propriétés au plus tôt dans le cycle de vie de l’application ?

Comment Spring Boot gère les propriétés et résout leurs valeurs ? Link to heading

La gestion des properties côté Spring Framework se fait essentiellement grâce à 3 classes / interfaces :

  • PropertySource<T> qui sert à incarner une source de paires clé/valeur (les propriétés) qui partagent une origine commune (fichier de propriétés, arguments de la ligne de commande, variables d’environnement). T représente le type sous-jacent qui contient les propriétés (par exemple java.util.Properties, java.util.Map).
  • PropertySources qui aggrège les PropertySource<T> en une collection unique pour un environnement Spring donné. En pratique, c’est l’implémentation MutablePropertySources qui est utilisée
  • PropertyResolver qui permet la résolution des propriétés pour un environnement Spring au sein d’une instance de PropertySources dans le respect de la préséance entre les PropertySource.

Le diagramme ci-dessous présente les 4 classes / interfaces citées ci-dessus avec les liens existants entre eux et l’environnement (seuls les composants des objets pertinents pour la compréhension sont représentés) :

classDiagram PropertyResolver <|-- ConfigurablePropertyResolver AbstractEnvironment --> "propertyResolver" ConfigurablePropertyResolver AbstractEnvironment --> "propertySources" MutablePropertySources MutablePropertySources --> "propertySourceList" PropertySource~T~ PropertySources <|-- MutablePropertySources class AbstractEnvironment { <<abstract>> } class PropertyResolver{ <<interface>> +containsProperty(String) boolean* +getProperty(String) String* +getProperty(String, Class~T~) T* } class ConfigurablePropertyResolver { <<interface>> } class PropertySource~T~ { <<abstract>> #T source #String name +getProperty(String) Object* } class PropertySources { <<interface>> +stream() Stream~PropertySource~?~~* +contains(String) boolean* +get(String) PropertySource~?~* }

Le diagramme ci-dessous présente la hiérarchie des classes de PropertySources (seuls les composants des objets pertinents pour la compréhension sont représentés) :

classDiagram Iterable~PropertySource~ <|-- PropertySources PropertySources <|-- MutablePropertySources class Iterable~PropertySource~ { <<interface>> } class MutablePropertySources { -List~PropertySource~?~~ propertySourceList +stream() Stream~PropertySource~?~~ +contains(String) boolean +get(String) PropertySource~?~ +addFirst(PropertySource~?~) void +addLast(PropertySource~?~) void +addBefore(String, PropertySource~?~) void +addAfter(String, PropertySource~?~) void +remove(String) PropertySource~?~ +replace(String, PropertySource~?~) void } class PropertySources { <<interface>> +stream() Stream~PropertySource~?~~* +contains(String) boolean* +get(String) PropertySource~?~* }

Le diagramme ci-dessous présente la hiérarchie des classes de PropertyResolver (seuls les composants des objets pertinents pour la compréhension sont représentés) :

classDiagram PropertyResolver <|-- ConfigurablePropertyResolver ConfigurablePropertyResolver <|-- ConfigurableEnvironment ConfigurablePropertyResolver <|-- AbstractPropertyResolver AbstractPropertyResolver <|-- PropertySourcesPropertyResolver AbstractPropertyResolver <|-- ConfigurationPropertySourcesPropertyResolver ConfigurableEnvironment <|-- AbstractEnvironment AbstractEnvironment --> "propertyResolver" ConfigurablePropertyResolver class PropertySourcesPropertyResolver{ -PropertySources propertySources } class ConfigurationPropertySourcesPropertyResolver{ -MutablePropertySources propertySources } class ConfigurableEnvironment{ <<interface>> +getPropertySources() MutablePropertySources* } class AbstractEnvironment { <<abstract>> } class PropertyResolver{ <<interface>> +containsProperty(String) boolean* +getProperty(String) String* +getProperty(String, Class~T~ targetType) T* } class ConfigurablePropertyResolver{ <<interface>> } class AbstractPropertyResolver{ <<abstract>> }

Le diagramme ci-dessous présente la hiérarchie des classes de PropertySource<T> (seuls les composants des objets pertinents pour la compréhension sont représentés) :

classDiagram PropertySource~T~ <|-- EnumerablePropertySource~T~ class EnumerablePropertySource~T~{ <<abstract>> +getPropertyNames() String[]* } class PropertySource~T~ { <<abstract>> #T source #String name containsProperty(String) boolean +getProperty(String) Object* }

Détection des propriétés au démarrage de Spring Boot Link to heading

Lors du lancement d’une application Spring Boot, la plupart des sources de propriétés sont lues et leurs propriétés chargées en mémoire au moment de la création de l’Environment qui est effectuée par la méthode org.springframework.boot.SpringApplication#prepareEnvironment. En outre les sources de propriétés sont également référencées par un objet MutablePropertySources dans un ordre correct permettant de suivre les règles de préséance définies au niveau de la fonctionnalité d’externalisation de la configuration

La création de l’environnement intervenant très tôt dans le cycle de vie de l’application (c’est la première chose qui est faite après le chargement des listeners), les propriétés ordonnées et leurs valeurs sont donc disponibles avant le chargement du contexte applicatif et leur utilisation par les beans.

Principe de résolution des propriétés dans Spring selon les règles de préséance Link to heading

Nous avons vu qu’au moment du chargement des propriétés, les sources de propriétés sont référencées par un objet MutablePropertySources dans un ordre bien précis permettant de répondre à la préséance définie dans au niveau de la fonctionnalité d’externalisation de la configuration : les PropertySource sont référencées dans une collection ordonnéee par priorité décroissante.

Dans Spring comme dans Spring Boot, lorsque les implémentations de PropertyResolver doivent résoudre la valeur d’une propriété via les méthodes getProperty(String key) ou getProperty(String key, Class<T> targetType), elles vont donc itérer sur la collection ordonnée par priorité décroissante de PropertySource de l’objet MutablePropertySources associé et retourner la valeur dès qu’une PropertySource fournit une valeur pour la clé de propriété demandée. Ainsi la préséance dans la résolution des propriétés est respectée.

Objets spécifiques à Spring Boot pour représenter les propriétés Link to heading

Spring Boot utilise des objets spécifiques pour représenter les propriétés et leurs sources afin d’apporter des fonctionnalités supplémentaires : les propriétés sont représentées par des instances de la classe ConfigurationProperty. Cette classe représente une propriété avec les données suivantes :

  • le nom de la propriété sous forme d’une instance de ConfigurationPropertyName
  • la valeur de la propriété
  • la source d’appartenance de la propriété
  • l’origine de la propriété (et de sa valeur) sous forme d’une instance de Origin

L’utilisation de la classe ConfigurationPropertyName pour représenter le nom de la propriété permet à Spring Boot de proposer des fonctionnalités plus riches pour la gestion des propriétés comme le relaxed binding pour les ConfigurationProperties

L’utilisation de Origin permet d’indiquer l’origine d’une propriété : c’est cette donnée qui permettra de répondre au problème de l’affichage précoce des propriétés avec leur valeur et leur origine.

Nous utiliserons donc les équivalents Spring Boot des objets décrits ci-dessus utilisés par Spring pour gérer les propriétés. Les algorithmes de résolution des valeurs des propriétés suivent les mêmes implémentations, la différence tient dans les métadonnées qui enrichissent les objets Spring Boot.

Correspondance entre les objets de Spring et Spring Boot pour la représentation des propriétés et de leurs sources Link to heading

Spring coreSpring Boot
Nom (clé) de la propriétéStringConfigurationPropertyName
Paire clé valeurVariable suivant les cas (par exemple Map.Entry)ConfigurationProperty
Source de propriétésPropertySourceConfigurationPropertySource
Ensemble de sources de propriétésPropertySources (en réalité MutablePropertySources)SpringConfigurationPropertySources
Résolveur de propriétés pour un ensemble de sourcesPropertyResolverConfigurationPropertySourcesPropertyResolver

NB : la classe SpringConfigurationPropertySources n’est pas publique et l’accès aux différentes ConfigurationPropertySource d’un environnement se fait grâce à la méthode statique ConfigurationPropertySource#from

Comment connaître les clés de propriété disponibles dans une application Spring avec les PropertySource ? Link to heading

Afin d’afficher les propriétés disponibles dans une application Spring Boot, il est nécessaire de pouvoir lister les clés disponibles à partir d’un Environment Spring. Cela peut se faire en deux étapes :

  1. Lister les PropertySource de l’Environment
  2. Pour chaque PropertySource, lister les noms de propriétés

ConfigurableEnvironment pour énumérer les PropertySource de l’Environment Link to heading

ConfigurableEnvironment permet d’avoir accès aux objets PropertySource référencés par l’environnement via la méthode ConfigurableEnvironment#getPropertySources qui retourne un objet de type MutablePropertySources (qui permet d’itérer sur les PropertySource qu’il référence). La quasi-totalité des Environment dans Spring sont des ConfigurableEnvironment : il suffit donc de récupérer l’instance d’Environment de l’application et de la caster en ConfigurableEnvironment. L’instance d’Environment quant à elle peut être retrouvée via ApplicationContext#getEnvironment dès lors que l’application est initialisée ou bien de manière bien plus précoce via ApplicationEnvironmentPreparedEvent dans le cas d’une application Spring Boot.

EnumerablePropertySource pour lister les noms de propriétés Link to heading

EnumerablePropertySource permet d’énumérer les noms des propriétés qui composent la source grâce à la méthode getPropertyNames. La plupart des sources de propriétés (fichiers, propriétés systèmes, arguments de la ligne de commande …) sont du type EnumerablePropertySource et permettent donc de connaître les propriétés qu’elles apportent au contexte de l’application. Les sources de propriétés non énumérables comme JndiPropertySource restent minoritaires.

Bloc de code résumant les éléments présentés dans cette section Link to heading

public Set<String> findAllPropertyKeys(ApplicationContext applicationContext) {
    return ((ConfigurableEnvironment) applicationContext.getEnvironment()).getPropertySources().stream()
            .filter(EnumerablePropertySource.class::isInstance)
            .map(EnumerablePropertySource.class::cast)
            .map(EnumerablePropertySource::getPropertyNames)
            .flatMap(Arrays::stream)
            .collect(Collectors.toSet());
}

Comment connaître l’origine de la valeur d’une propriété avec l’API Origin de Spring Boot ? Link to heading

Lorsqu’une même propriété (même clé) est présente dans plusieurs sources de propriétés à la fois, sa valeur sera définie en suivant les règles de préséance définies au niveau de la fonctionnalité d’externalisation de la configuration. Dans certaines situations, la multiplicité des sources ainsi que la flexibilité offerte sur les noms de propriétés par Spring peut rendre délicate la détermination de la valeur qui sera vraiment utilisée par Spring pour une propriété. C’est pourquoi il peut être utile de déterminer l’origine de la valeur d’une propriété.

L’API Origin de Spring Boot Link to heading

Comme vu ci-dessus, l’origine d’une propriété se représente dans Spring Boot à travers une instance de l’interface org.springframework.boot.origin.Origin associée à une ConfigurationProperty. L’instance d’Origin est susceptible de fournir une information sur l’origine de la valeur incarnée par ConfigurationProperty#value via par exemple un appel à Origin#toString. Ce qui donnerait une sortie de la forme :

class path resource [application.properties] - 2:29

La sortie donnée en exemple indique que la valeur vient du fichier application.properties trouvé par Spring Boot dans le classpath et se trouve ligne 2, colonne 29 (la colonne indique le premier caractère de la valeur de la propriété après le = et tout espace qui le suivrait).

Accès à l’Origin d’une propriété dans une source donnée Link to heading

Comme indiqué dans le tableau ci-dessus, c’est un objet ConfigurationPropertySource qui permet d’accéder aux propriétés d’une PropretySource de Spring sous forme de ConfigurationProperty. Cela peut se faire grâce à la méthode ConfigurationPropertySource#getConfigurationProperty en lui pasant une instance de
ConfigurationPropertyName (nom de la propriété).

L’objet ConfigurationPropertySource associé à une source de propriétés quant à lui peut être obtenu via un appel à ConfigurationPropertySource#from

Quant à l’instance de ConfigurationPropertyName on l’obtient à partir de la factory statique ConfigurationPropertyName#of avec le nom (la clé) de la propriété en argument.

Comment déterminer la bonne origine Link to heading

Avec le principe énoncé ci-dessus, il y aura autant d’origines pour une propriété que de sources de propriétés fournissant une valeur pour la clé. Afin de trouver l’origine qui correspond à la valeur retenue par Spring Boot dans le respect de la préséance entre les PropertySource, il faut suivre le même algorithme de résolution que celui défini plus haut.

La solution consiste donc à vérifier les objets Origin renvoyés par chaque PropertySource (préalablement transformée en ConfigurationPropertySource) en respectant l’ordre dans lequel les PropertySource sont stockées dans l’objet MutablePropertySources associé à l’environnement. Le premier objet Origin non null correspond très vraisemblablement à l’origine de la valeur qui sera retournée par Spring Boot pour la propriété.

On peut résumer les éléments présentés dans cette section avec le bloc de code suivant Link to heading

public Optional<String> findPropertyOrigin(String key, ApplicationContext applicationContext) {
    ConfigurationPropertyName configurationPropertyName = ConfigurationPropertyName.of(key);
    Environment environment = applicationContext.getEnvironment();
    return ((ConfigurableEnvironment) environment).getPropertySources().stream()
            .map(ConfigurationPropertySource::from)
            .filter(Objects::nonNull)
            .map(configurationPropertySource -> configurationPropertySource.getConfigurationProperty(configurationPropertyName))
            .filter(Objects::nonNull)
            .map(ConfigurationProperty::getOrigin)
            .filter(Objects::nonNull)
            .map(Origin::toString)
            .findFirst();
}

Comment afficher les propriétés au plus tôt dans le cycle de vie de l’application ? Link to heading

De l’intérêt d’afficher au plus tôt les propriétés utilisées par l’application Link to heading

L’affichage des propriétés dans les applications se fait parfois au cours de l’instanciation d’un bean ou de l’appel d’une méthode @PostConstruct. Cependant si une propriété mal valorisée fait échouer la définition ou l’instanciation des beans précédents dans le contexte, il se peut que le bean chargé d’afficher les propriétés ne soit jamais instancié. Ainsi l’application s’arrête sans indication sur les valeurs des propriétés utilisées. La méthode qui suit permet de se prémunir de cet affichage tardif des propriétés.

SpringApplicationEvent lié à la création de l’environnement Link to heading

Lorsqu’une application Spring Boot démarre, la plupart des propriétés sont résolues très tôt dans le cycle de vie de l’application (voir la documentation sur les listeners spring boot pour avoir une idée du cycle de vie d’une application Spring Boot) : au moment de la création de l’Environment qui coïncide avec l’émission de l’évènement ApplicationEnvironmentPreparedEvent. À partir de ce moment, la plupart des sources de propriétés ont été inspectées par Spring Boot et les valeurs des propriétés résolues : il est donc possible dès ce moment de retrouver les valeurs réellement utilisées pour les propriétés ainsi que leurs origines.

Récupérer les propriétés et leurs origines à partir de ApplicationEnvironmentPreparedEvent Link to heading

L’évènement ApplicationEnvironmentPreparedEvent émis par Spring Boot permet de récupérer l’objet Environment juste après sa création et avant que Spring n’entame d’autres phases du démarrage de l’application. La récupération de l’instance d’Environment se fait en référençant en tant que listener de l’application Spring Boot une classe implémentant ApplicationListener<ApplicationEnvironmentPreparedEvent>. L’Environment sera disponible via l’objet ApplicationEnvironmentPreparedEvent qui sera passé à la méthode onApplicationEvent

Synthèse des codes vus ci-dessous dans une classe permettant l’affichage des propriétés à la création de l’environnement Link to heading

Associé aux autres exemples présentés plus hauts, voici ce que donnerait par exemple la classe principale d’une application qui affiche dès la création de son environnement ses propriétés, leurs valeurs et leurs origines :

package poc;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException;
import org.springframework.boot.origin.Origin;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

@SpringBootApplication
public class Main {
    void main(String[] args) {
        new SpringApplicationBuilder(Main.class)
                .listeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) Main::onApplicationEvent)
                .build().run(args);
    }

    private static void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        final ConfigurableEnvironment configurableEnvironment = event.getEnvironment();
        final List<ConfigurationPropertySource> configurationPropertySources = extractConfigurationPropertySources(configurableEnvironment);
        findAllPropertyKeys(configurableEnvironment)
                .map(key -> {
                    String value = configurableEnvironment.getProperty(key);
                    Optional<String> origin = findPropertyOrigin(key, configurationPropertySources);
                    return key + " = " + value + origin.map(" ### FROM "::concat).orElse("");
                })
                .forEach(IO::println);
    }

    private static List<ConfigurationPropertySource> extractConfigurationPropertySources(ConfigurableEnvironment configurableEnvironment) {
        return configurableEnvironment.getPropertySources().stream()
                .map(ConfigurationPropertySource::from)
                .filter(Objects::nonNull)
                .toList();
    }

    private static Stream<String> findAllPropertyKeys(ConfigurableEnvironment configurableEnvironment) {
        return configurableEnvironment.getPropertySources().stream()
                .filter(EnumerablePropertySource.class::isInstance)
                .map(EnumerablePropertySource.class::cast)
                .map(EnumerablePropertySource::getPropertyNames)
                .flatMap(Arrays::stream)
                .distinct();
    }

    private static Optional<String> findPropertyOrigin(String key, List<ConfigurationPropertySource> configurationPropertySources) {
        try {
            final ConfigurationPropertyName configurationPropertyName = ConfigurationPropertyName.of(key);
            return configurationPropertySources.stream()
                    .map(configurationPropertySource -> configurationPropertySource.getConfigurationProperty(configurationPropertyName))
                    .filter(Objects::nonNull)
                    .map(ConfigurationProperty::getOrigin)
                    .filter(Objects::nonNull)
                    .map(Origin::toString)
                    .findFirst();
        } catch (InvalidConfigurationPropertyNameException e) {
            return  Optional.empty();
        }
    }
}

NB : Même s’il est très probable que l’origine et la valeur affichées pour la propriété soit celles qui seront prises en compte par Spring Boot, cela n’est pas garanti : certaines sources de propriétés sont résolues lors de la création du contexte applicatif (en tant que Beans Spring ou par des Beans Spring) donc après la création de l’environnement et l’exécution du code ci-dessus. C’est par exemple le cas des fichiers référencés par des annotations @PropertySource, des propriétés introduites par les méthodes annotées @DynamicPropertySource dans les tests ou des propriétés venant des paramètres d’initialisation d’un conteneur de servlets.

Utiliser une librairie tierce pour afficher systématiquement le contenu et l’origine des propriétés dans une application Spring Boot Link to heading

En intégrant le code ci-dessus dans une application Spring Boot, on bénéficiera de l’affichage précoce des propriétés avec leurs valeurs et leurs origines. Cependant, il restera à gérer d’autres aspects :

  • limiter les propriétés qui seront affichées (si l’affichage se révèle trop verbeux)
  • les sources utilisées pour récupérer les clés des propriétés (si l’affichage se révèle trop verbeux)
  • masquer les secrets
  • éviter les exceptions pour les propriétés qui dépendent de placeholders absents
  • signaler les sources de propriété non énumérables
  • formatter l’affichage pour rendre lisible l’affichage des propriétés et leurs valeurs
  • pouvoir désactiver l’affichage

En outre, l’intégration du code au moyen d’un copier-coller est une mauvaise pratique et il est préférable de pouvoir adjoindre le processus d’affichage des propriétés à l’application existante sans en modifier le code, par exemple en ajoutant une dépendance dans le classpath.

C’est ce que propose la librairie Spring-Boot-Properties-Logger qui rend tous les services ci-dessus simplement en l’ajoutant dans les dépendances de l’application et en configurant quelques propriétés pour la paramétrer :

<dependency>
    <groupId>io.github.fbibonne</groupId>
    <artifactId>boot-properties-logger-starter</artifactId>
    <version>2.3.0</version>
</dependency>
# Affichage des propriétés Spring ainsi que les propriétés du domaine com.example sur la base des préfixes des clés
properties.logger.prefix-for-properties=debug, trace, info, logging, spring, server, management, springdoc, properties, com.example

La librairie Spring-Boot-Properties-Logger gère l’affichage des propriétés, de leurs valeurs et de leurs origines, mais aussi des sources de propriétés détectées. Les sources de propriétés détectées ainsi que les propriétés dont les secrets sont à masquer sont aussi paramétrables.

Afin de pouvoir s’enregistrer comme listener auprès de l’application Spring Boot sans modifier le code applicatif, la librairie Spring-Boot-Properties-Logger utilise le mécanisme des fichiers spring.factories. Un fichier spring.factories contenant le nom du listener fourni par la librairie implémentant org.springframework.context.ApplicationListener se trouve dans le dossier META-INF du jar de la librairie : c’est par ce truchement que le listener en question est passé à l’application Spring Boot et appelé lorsque l’évènement ApplicationEnvironmentPreparedEvent est émis.