<
Media
>
Article

maven-enforcer-plugin : imposer des règles de dépendances et fiabiliser vos builds Maven

7
min
10
/
11
/
2026

Introduction

Dans un projet Maven, rien n'empêche un développeur d'importer n'importe quelle dépendance, ou d'utiliser une version SNAPSHOT en production, au risque d'introduire des vulnérabilités, des incompatibilités ou une instabilité du build. maven-enforcer-plugin permet d’éviter ces dérives, en appliquant des règles strictes, tout en restant simple à configurer. Je vous présenterai d'abord quelques règles courantes, puis je ferai un focus sur son usage pour respecter les principes de l'architecture hexagonale.

Principes clés

  • Les règles sont des contraintes appliquées à l'application sur les versions, dépendances, structure, etc. (liste complète : https://maven.apache.org/enforcer/enforcer-rules/index.html).
  • Par défaut, le plugin s'active lors de la phase d'exécution validate, ce qui permet l'échec rapide du build.
  • La configuration s'effectue dans le pom.xml, soit globalement soit par module, avec la possibilité de personnaliser les messages d’erreurs.
  • Le plugin effectue une vérification globale, c'est à dire que toutes les règles sont vérifiées lors de l'exécution. Il est possible de changer ce comportement, avec le paramètre failFast qui permet de s'arrêter à la première erreur.

Configuration de base

Cette configuration minimale attache l’objectif enforce au cycle de build afin d’exécuter les règles dès la phase validate et d’échouer rapidement en cas de violation.

<pre><code>&lt;project>
 &lt;build>
   &lt;plugins>
     &lt;plugin>
       &lt;groupId>org.apache.maven.plugins&lt;/groupId>
       &lt;artifactId>maven-enforcer-plugin&lt;/artifactId>
       &lt;version>3.6.2&lt;/version>
       &lt;executions>
         &lt;execution>
           &lt;id>enforce-java&lt;/id>
           &lt;phase>validate&lt;/phase>
&lt;!-- phase par défaut, mais qui peut être changée -->
           &lt;goals>&lt;goal>enforce&lt;/goal>&lt;/goals>
           &lt;configuration>
             &lt;rules>
               
&lt;!-- insérer ici toutes les règles à respecter -->
             &lt;/rules>
           &lt;/configuration>
         &lt;/execution>
       &lt;/executions>
     &lt;/plugin>
   &lt;/plugins>
 &lt;/build>
&lt;/project>
</code></pre>

Cas d’usages courants

Forcer la présence de propriétés

Il est parfois important de s'assurer que certaines propriétés sont définies dans le POM, notamment pour éviter les valeurs par défaut implicites.

<pre><code>&lt;rules>
 &lt;requireProperty>
   &lt;property>some.property&lt;/property>
 &lt;/requireProperty>
&lt;/rules>
</code></pre>

La règle requireProperty permet d'exiger la présence de &lt;properties>&lt;some.property>foo< /some.property>&lt;/properties> dans le POM. On peut ainsi imposer la définition de java.version, et faire un couplage avec la règle requireJavaVersion (voir ci-dessous), pour s'assurer que la version du JDK est définie explicitement.

Pour contrôler uniquement la validation, on peut se contenter de la commande suivante :

<pre><code>mvn validate</code></pre>

Si la propriété n'est pas définie, le build échoue avec un message d'erreur clair :

<pre><code>[INFO] --- maven-enforcer-plugin:3.0.0:enforce (default) @ my-artifact ---
[WARNING] Rule 1: org.apache.maven.plugins.enforcer.RequireProperty failed with message:
Property "some.property" is required for this build.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------</code></pre>

Si la propriété est définie, le build continue et la règle est validée :

<pre><code>[INFO] --- maven-enforcer-plugin:3.0.0:enforce (default) @ my-artifact ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------</code></pre>

Ce message est le même à chaque fois que les règles sont respectées, quelles que soient les règles appliquées.

Exiger une version Java minimale

S'assurer que java.version est défini dans le POM est une bonne pratique, mais il est aussi important de vérifier que la version du JDK utilisée pour compiler est compatible avec les fonctionnalités utilisées dans le code.

<pre><code>&lt;rules>
 &lt;requireJavaVersion>
   &lt;version>[25,)&lt;/version>
 &lt;/requireJavaVersion>
&lt;/rules>
</code></pre>

Dans cet exemple, on impose un JDK 25 ou supérieur. La syntaxe des ranges permet de cibler une version exacte, une liste exhaustive ou non exhaustive comme dans l'exemple ci-dessus (plus d'infos).

Pour limiter la duplication, on peut aussi utiliser :

<pre><code>&lt;version>[${java.version},)&lt;/version></code></pre>

Si la version de Java utilisée pour compiler est inférieure à la version minimale, le build échoue :

<pre><code>[INFO] --- maven-enforcer-plugin:3.0.0:enforce (default) @ my-artifact ---
[WARNING] Rule 1: org.apache.maven.plugins.enforcer.RequireJavaVersion failed with message:
Detected JDK Version: 21.0.4 is not in the allowed range [25,).
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------</code></pre>

Interdire certaines versions de dépendances

Pour sécuriser une release, il est possible d'interdire les versions de dépendances qui ne sont pas stables (ex : SNAPSHOT, 0.x.x, etc.).

<pre><code>&lt;rules>
 &lt;bannedDependencies>
   &lt;excludes>
     &lt;exclude>*:*:*-SNAPSHOT&lt;/exclude>
   &lt;/excludes>
   &lt;message>SNAPSHOT interdites&lt;/message>
 &lt;/bannedDependencies>
 &lt;bannedDependencies>
   &lt;excludes>
     &lt;exclude>*:*:[0,1)&lt;/exclude>
   &lt;/excludes>
   &lt;message>Version 0.x.x interdites&lt;/message>
 &lt;/bannedDependencies>
&lt;/rules>
</code></pre>

Ici, on utilise plusieurs fois la même règle (bannedDependencies), mais avec des contraintes différentes, pour pouvoir afficher des messages d'erreur spécifiques à chaque cas.

Lorsque les règles sont enfreintes, le build échoue avec les messages spécifiques définis dans le POM, ainsi que les dépendances interdites détectées :

<pre><code>[INFO] --- maven-enforcer-plugin:3.0.0:enforce (default) @ my-artifact ---
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.BannedDependencies failed with message:
SNAPSHOT interdites
Found Banned Dependency: fr.younup:an-artifact:jar:1.1.0-SNAPSHOT
Use 'mvn dependency:tree' to locate the source of the banned dependencies.
[WARNING] Rule 1: org.apache.maven.plugins.enforcer.BannedDependencies failed with message:
Version 0.x.x interdites
Found Banned Dependency: fr.younup:other-artifacts:jar:0.1.14
Use 'mvn dependency:tree' to locate the source of the banned dependencies.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------</code></pre>

Éviter les conflits de versions de dépendances

Si une dépendance est récupérée par plusieurs chemins (directs ou transitifs), Maven n'en retient qu'une seule en utilisant le mécanisme nearest, puis first en cas d'égalité de profondeur.

Exemple nearest :

<pre><code>root
├─ A
│  └─ B:1.0.0
└─ C
  └─ D
     └─ B:1.1.0</code></pre>

Maven sélectionne B en version 1.0.0, car la profondeur est de 2 pour l'atteindre en partant de A, contre 3 en partant de C.

Exemple first :

<pre><code>├─ A
│  └─ B:1.0.0
└─ C
  └─ B:1.1.0</code></pre>

Maven sélectionne B en version 1.0.0, car la profondeur est la même pour les deux chemins, mais l'import de A est déclaré avant celui de C.

<pre><code>&lt;rules>
 &lt;requireUpperBoundDeps/>
&lt;/rules></code></pre>

Avec cette règle, la compilation aurait échoué, car requireUpperBoundDeps aura détecté que la version retenue pour B n'est pas la version la plus haute dans l'arbre des dépendances.

Pour régler le premier cas (nearest), il faut exclure B:1.0.0, ou importer explicitement B:1.1.0 (pour forcer un chemin encore plus court).

Pour régler le second cas (first), on peut utiliser les mêmes techniques ; changer l'ordre d'import des dépendances est aussi possible pour ce cas, mais plus risqué sur le long terme.

Exiger la version des plugins

Par défaut, la balise version des plugins est facultative. Maven tente de la déterminer en fonction du pluginManagement, de l'héritage parent (POM courant, parents, puis Super POM. Voir : Inheritance et Super POM). Le risque est donc que le build ne soit pas reproductible, car la version du plugin choisie peut être différente en fonction de l'environnement ou du moment du build.

<pre><code>&lt;rules>
 &lt;requirePluginVersions/>
&lt;/rules></code></pre>

Cette règle permet de s'assurer que les versions des plugins sont déclarées explicitement, dans le plugin ou le pluginManagement (pratique conseillée), pour éviter ces dérives.

Exiger la version de Maven

Nous venons de voir comment exiger la version des plugins, mais il est aussi possible d'exiger la version de Maven elle-même, pour s'assurer définitivement de la reproductibilité du build. En effet, le Super POM est fourni avec une version de Maven, et les plugins y sont déclarés avec des versions spécifiques.

<pre><code>&lt;rules>
 &lt;requireMavenVersion>
   &lt;version>3.9&lt;/version>
 &lt;/requireMavenVersion>
&lt;/rules></code></pre>

Dans cet exemple, on impose une version de Maven 3.9. En appliquant cette règle et la précédente, on s'assure donc d'une reproductibilité complète du build.

Application à l’architecture hexagonale

La règle de base de l'architecture hexagonale est de ne pas polluer le métier par la technique. En utilisant maven-enforcer-plugin, on cherche à garantir que ces couches internes (aka domain ou core) restent indépendantes des implémentations techniques (aka adapters ou infra), et n’importent pas de dépendances d’infrastructure (framework, bdd, etc.).

Stratégie générale :

  • Modulariser le projet (modules Maven distincts).
  • Appliquer des enforcements différents par module (core strict, adapters permissifs).
  • Exécuter en validate dans la CI pour bloquer le process si des règles sont enfreintes.

Bloquer l'import des dépendances


<pre><code>&lt;rules>
 &lt;bannedDependencies>
   &lt;excludes>
     &lt;!-- interdit tout artefact qui contient "adapter" -->
     &lt;exclude>*:*-adapter&lt;/exclude>
     &lt;exclude>fr.younup.adapters:*&lt;/exclude>
   &lt;/excludes>
   &lt;message>Le module core ne doit pas dépendre d'adapters/infra.&lt;/message>
 &lt;/bannedDependencies>
&lt;/rules>
</code></pre>

Ici, nous empêchons de déclarer l'import des dépendances techniques. Il faut donc placer cette configuration dans le POM du module domain/core.

Interdire l'import de frameworks d’infrastructure dans le domain

Typiquement, on veut interdire l'import de frameworks d’infrastructure, par exemple Spring ou les accès à la BDD :

<pre><code>&lt;bannedDependencies>
 &lt;excludes>
   &lt;exclude>org.springframework:*&lt;/exclude>
   &lt;exclude>com.zaxxer:HikariCP&lt;/exclude>
 &lt;/excludes>
 &lt;message>Domain/core ne doit pas dépendre de frameworks d'infrastructure.&lt;/message>
&lt;/bannedDependencies></code></pre>

Le principe est le même que pour la règle précédente, mais en élargissant le scope d'exclusion aux modules tiers.

Bloquer l'usage de certaines classes


<pre><code>&lt;bannedClasses>
 &lt;classes>
   &lt;class>fr.younup.adapters.**&lt;/class>
 &lt;/classes>
 &lt;message>Les packages adapters/infra ne doivent pas être référencés publiquement depuis domain/core.&lt;/message>
&lt;/bannedClasses>
</code></pre>

Ici, la dépendance pourrait être importée, mais pas utilisée dans le code. Au cas où la classe serait présente via une dépendance transitive, ou si le jar était ajouté au classpath sans passer par un import de dépendances. En cumulant avec la règle d'import des dépendances, c'est un peu ceinture et bretelles.

Règles pragmatiques

Comme souvent, il y a la théorie et la pratique, et l'idée n'est pas d'être trop strict. On peut donc définir des règles qui excluent tout par défaut, et mettre en whitelist les rares éléments indispensables au domain/core :

<pre><code>&lt;rules>
 &lt;bannedDependencies>    
   &lt;excludes>
     &lt;exclude>*:*&lt;/exclude> &lt;!-- Par défaut tout est interdit -->
   &lt;/excludes>
   &lt;includes> &lt;!-- Exceptions explicites (whitelist) -->      
     &lt;include>fr.younup.some.lib:lib-msg&lt;/include> &lt;!-- une lib de l'entreprise pour manipuler les messages de l'application -->
     &lt;include>*:*:*:*:test&lt;/include> &lt;!-- autoriser toutes les dépendances en scope test -->
   &lt;/includes>
   &lt;message>Le module core/domain ne doit pas dépendre d'artefacts d'infrastructure.&lt;/message>
 &lt;/bannedDependencies>
&lt;/rules></code></pre>

C'est généralement ce que j'utilise comme règle dans mon module domain/core.

Workflow recommandé

Il est facile de se retrouver avec un build qui échoue à cause de règles trop strictes, ou trop complexes, alliés à des messages peu clairs.

Pour la mise en place, je recommande donc de suivre ces étapes :

  • Commencez avec le mode non bloquant (log).
  • Testez en local avant de pousser en CI.
  • Documentez les règles et messages pour guider les développeurs.
  • Lorsque la règle vous convient, passez-la en mode bloquant pour que la CI plante en cas de violation des règles.

Bonnes pratiques et limites

Dans la même logique, n'oubliez pas que le but est de sécuriser le build, mais pas de le rendre impossible à faire évoluer.

Commencez par des règles simples et critiques (Java version, bannedDependencies), avant de complexifier.

Pensez à définir des messages clairs, voire qui suggèrent des actions correctives.

Ne cherchez pas à tout contrôler. Plus vous aurez de règles, plus vous prenez le risque de réduire votre agilité.

Enfin, pour des vérifications très spécifiques, préférez une règle custom, ou un outil dédié d’analyse statique.

Conclusion

Nous avons vu tout au long de cet article la facilité de définition des règles et les bénéfices qu'elles peuvent accorder.

Pour vous lancer, je vous propose donc de retenir ces 3 étapes clés :

  1. Modularisez votre projet
  2. Ajoutez maven-enforcer-plugin au pom.xml de chaque module pour garantir :
    1. La reproductibilité du build (version de Java, de Maven, des plugins)
    2. Les conflits de dépendances
    3. Un code métier non pollué
  3. Activez la validation au plus tôt dans la CI, pour bloquer le build et économiser des ressources.

Et même si vous ne faites pas d'architecture hexagonale, ce plugin reste un excellent outil pour sécuriser vos builds.

No items found.
ça t’a plu ?
Partage ce contenu
Jean-Yves

Amoureux du dév depuis toujours, Jean-Yves se souvient, avec presqu'une pointe de nostalgie, des ses premiers programmes en GW/Basic ou en Turbo Pascal !

Aujourd'hui, c'est plutôt en Java qu'il développe mais peu importe le langage, c'est l'évolutivité et la maintenabilité qui lui tiennent à cœur.

Et quand il n'est pas derrière son PC, il est sûrement occupé à cuisiner, à bouquiner ou parti sur les chemins pour préparer une marche Audax.