How would you create a custom Enforcer rule, and how do you roll out Enforcer policy across many projects without crippling developer productivity?
answer
- implement EnforcerRule / AbstractEnforcerRule
- throw EnforcerRuleException to fail
- package as jar, add as plugin dependency
- centralise in parent POM
- phase in: warn -> fail, enforcer.skip escape hatch
basics
~20 sWrite a class implementing EnforcerRule (or the newer EnforcerRule2/abstract base), package it as a jar, and add it as a plugin dependency so it appears in the rules list. Roll out policy via a shared parent POM, start in warn mode, and use fail=false or skip flags to phase in strictness.
solid answer
~40 sA custom rule implements the plugin's rule interface (classic `EnforcerRule#execute`, throwing `EnforcerRuleException` to fail; newer plugin versions use an injectable `AbstractEnforcerRule`). You build it into a jar and declare that jar as a `<dependency>` of the enforcer plugin, then reference your rule element in `<rules>`. For governance: put the plugin config in a **shared parent/BOM POM** so every project inherits identical rules. Introduce new rules gradually — set `<fail>false</fail>` (or `failFast`/`<level>WARN</level>` in newer versions) so violations warn before they block, and expose a property like `-Denforcer.skip=true` for emergencies. Wire `enforce` into CI on `validate`. The art is balancing hard gates (security CVEs, banned libs) against soft, fixable nudges (convergence) so the policy is respected rather than routinely bypassed.
code
bash · 4 lines# temporary escape hatch for an emergency build
mvn package -Denforcer.skip=true
# or warn-only for a specific run
mvn package -Denforcer.fail=falsego deeper
Aware that custom rules exist but typically uses built-ins.
Can add a published custom-rule jar as a plugin dependency and reference it.
Implements a custom rule and chooses fail vs warn for it.
Designs the org-wide rollout: parent POM distribution, tiered fail/warn policy, escape hatches, CI integration, and performance budget.
## Writing a custom rule Built-in rules cover common cases, but org-specific policy (e.g. 'every module must declare a `<scm>` block' or 'no SNAPSHOT dependencies in a release') needs custom logic. Classic API (widely deployed): implement `org.apache.maven.enforcer.rule.api.EnforcerRule` and put your check in `execute(...)`, throwing `EnforcerRuleException` to fail the build. Newer plugin lines (3.x) provide `AbstractEnforcerRule` with field injection of components (project, log). ```java public class RequireNoSnapshots extends AbstractEnforcerRule { @Inject private MavenProject project; @Override public void execute() throws EnforcerRuleException { boolean snap = project.getDependencies().stream() .anyMatch(d -> d.getVersion() != null && d.getVersion().endsWith("-SNAPSHOT")); if (snap) throw new EnforcerRuleException("No SNAPSHOT dependencies allowed in this profile."); } } ``` ## Packaging and using it Build the rule into its own jar, publish it, then add it as a dependency of the enforcer plugin and reference it in the rules list: ```xml <plugin> <artifactId>maven-enforcer-plugin</artifactId> <version>3.5.0</version> <dependencies> <dependency> <groupId>com.acme</groupId> <artifactId>acme-enforcer-rules</artifactId> <version>1.0.0</version> </dependency> </dependencies> <executions> <execution> <goals><goal>enforce</goal></goals> <configuration> <rules> <requireNoSnapshots implementation="com.acme.RequireNoSnapshots"/> </rules> </configuration> </execution> </executions> </plugin> ``` ## Governance across many projects 1. **Centralise in a parent POM / company BOM** — projects inherit the plugin and rules, so policy changes ship once. 2. **Phase in strictness** — for a new rule, start with `<fail>false</fail>` (warn only) or, in newer versions, `<level>WARN</level>` / `failFast=false`, so teams see violations before they block. 3. **Provide escape hatches** — `-Denforcer.skip=true` or `-Denforcer.fail=false` for genuine emergencies; track and review their use. 4. **Tier the rules** — hard-fail on security (banned CVE versions, banned libs); soft-warn on hygiene (convergence) that requires broad cleanup. 5. **Run in CI on `validate`** so the gate is enforced uniformly and fails fast. ## Trade-offs Over-strict policy that constantly blocks unrelated work breeds `-Denforcer.skip=true` habits that defeat the purpose. The principal-level skill is sequencing rollout, choosing fail-vs-warn per rule, and keeping the rule set fast (Enforcer runs every build).
- How does a custom rule signal failure?By throwing an EnforcerRuleException from its execute method; the message becomes the build-failure text.
- How do you introduce a strict new rule without breaking every team overnight?Ship it in warn mode first (fail=false / level=WARN) in the shared parent POM, give teams time to fix violations, then flip it to hard-fail.
- What is the risk of overly strict Enforcer policy?Developers start passing -Denforcer.skip=true routinely, so the gate is bypassed and provides false assurance.
saying these in an interview costs you the question
- Editing each project's POM individually instead of centralising in a parent — unmaintainable.
- Shipping a new hard-fail rule with no warn period or escape hatch.
- Forgetting the rule jar must be a dependency of the enforcer plugin, not the project.