Add a Spring AI 2.0.2 starter to a Spring Boot 4.0.9 project, and mvn validate stops the build. It only happens when the Maven Enforcer requireUpperBoundDeps rule is enabled. The log says Require upper bound dependencies error for org.springframework.boot:spring-boot-starter-restclient:4.0.9. Paths to dependency are:.
The Spring AI 2.0.2 POMs request Boot starters 4.1.2 and
micrometer-core1.17.2 with explicit versions. The Boot 4.0.9 parent manages them down to 4.0.9 and 1.16.8, andrequireUpperBoundDepstreats that downgrade as an error.
The resolved versions and the libraries inside the jar are all 4.0.9-based, and the app starts normally.
Fix it by moving Boot to 4.1.2 (the version the Spring AI 2.0.2 POMs request) or by adding the four artifacts to the rule’sexcludes.
Environment
I built the same app against a Boot 4.0.9 parent and a Boot 4.1.2 parent and compared the results. To prove the app really runs, it sends one prompt to qwen3:4b on a local Ollama server and exits.
| Item | Version |
|---|---|
| Spring Boot (reproduction and excludes fix) | 4.0.9 (Micrometer 1.16.8) |
| Spring Boot (control) | 4.1.2 (Micrometer 1.17.2) |
| Spring AI | 2.0.2 |
| maven-enforcer-plugin | 3.6.3 |
| JDK | 21.0.12.1 (Temurin) |
| Maven | 3.10.0 |
| Ollama | 0.40.11, model qwen3:4b |
The lab has three projects. boot40-strict reproduces the error, boot41 is a control that only changes the parent, and boot40-excludes adds rule exclusions. All three share the same Java sources.
Reproduce: Boot 4.0.9 + Spring AI 2.0.2 fails only requireUpperBoundDeps
The pom.xml
The project uses the Boot 4.0.9 parent, imports spring-ai-bom 2.0.2, and adds a single Ollama starter. This is the setup the official docs describe.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.9</version>
<relativePath/>
</parent>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.2</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
</dependencies>
On top of that, two Enforcer rules are enabled. This is the kind of setup teams use to catch dependency version conflicts at build time.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>enforce-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
The mvn validate output
Running mvn validate passes dependencyConvergence and fails only requireUpperBoundDeps.
$ cd boot40-strict && mvn -B -Dmaven.repo.local=../.tools/m2/repository validate
...
[INFO] --- enforcer:3.6.3:enforce (enforce-dependencies) @ ai-boot40-strict ---
[INFO] Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence passed
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
...
[ERROR] Rule 1: org.apache.maven.enforcer.rules.dependency.RequireUpperBoundDeps failed with message:
[ERROR] Failed while enforcing RequireUpperBoundDeps. The error(s) are [
[ERROR] Require upper bound dependencies error for org.springframework.boot:spring-boot-starter-restclient:4.0.9. Paths to dependency are:
[ERROR] +-com.example.aiboot:ai-boot40-strict:0.0.1
[ERROR] +-org.springframework.ai:spring-ai-starter-model-ollama:2.0.2
[ERROR] +-org.springframework.boot:spring-boot-starter-restclient:4.0.9 (managed) <-- org.springframework.boot:spring-boot-starter-restclient:4.1.2
[ERROR] ,
[ERROR] Require upper bound dependencies error for org.springframework.boot:spring-boot-starter-webclient:4.0.9. Paths to dependency are:
[ERROR] +-com.example.aiboot:ai-boot40-strict:0.0.1
[ERROR] +-org.springframework.ai:spring-ai-starter-model-ollama:2.0.2
[ERROR] +-org.springframework.boot:spring-boot-starter-webclient:4.0.9 (managed) <-- org.springframework.boot:spring-boot-starter-webclient:4.1.2
[ERROR] ,
[ERROR] Require upper bound dependencies error for io.micrometer:micrometer-core:1.16.8. Paths to dependency are:
...
[ERROR] +-io.micrometer:micrometer-core:1.16.8 (managed) <-- io.micrometer:micrometer-core:1.17.2
[ERROR] ,
[ERROR] Require upper bound dependencies error for io.micrometer:micrometer-observation:1.16.8. Paths to dependency are:
...
[ERROR] +-io.micrometer:micrometer-observation:1.16.8 (managed) <-- io.micrometer:micrometer-observation:1.17.2
There are four errors: spring-boot-starter-restclient, spring-boot-starter-webclient, micrometer-core, and micrometer-observation. Every one has the form A (managed) <-- B. The left side is the version Maven actually picked. The right side is the version a Spring AI POM asked for.
This isn’t specific to the Ollama starter. The original report, spring-ai issue #6465, hit the same four errors with the Mistral AI starter on Boot 4.0.7 and Spring AI 2.0.0. The numbers differ, but the structure is the same.
Cause: the Spring AI 2.0.2 POMs request Boot 4.1.2 directly
Versions written in the published POMs
I grepped the version lines out of two Spring AI 2.0.2 POMs in the local repository. The starter POM pins the Boot starters to 4.1.2, and the spring-ai-commons POM pins Micrometer to 1.17.2.
$ grep -B2 -A1 "<version>4.1.2</version>" .tools/m2/repository/org/springframework/ai/spring-ai-starter-model-ollama/2.0.2/spring-ai-starter-model-ollama-2.0.2.pom
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webclient</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
--
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
$ grep -B2 -A1 "<version>1.17.2</version>" .tools/m2/repository/org/springframework/ai/spring-ai-commons/2.0.2/spring-ai-commons-2.0.2.pom
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.17.2</version>
<scope>compile</scope>
Spring AI 2.0.x artifacts are published against the newest Boot version available at build time. In the issue thread, a maintainer explained that 2.0.0 used 4.1.0 "because it’s the latest and greatest at the time of publication, while still remaining compatible with 4.0.0." Version 2.0.2 follows the same pattern and writes Boot 4.1.2.
Why 4.0.9 wins, and why the Enforcer calls it an error
The Boot 4.0.9 parent uses dependencyManagement to pin Boot modules to 4.0.9 and Micrometer to 1.16.8. The Maven docs say dependency management takes precedence over dependency mediation for transitive dependencies. So even though Spring AI asks for 4.1.2, Maven picks 4.0.9.
requireUpperBoundDeps is the rule that objects to this. Per its docs, the resolved version of a dependency must be at least the highest version found in the transitive dependencies. The request is 4.1.2 and the selection is 4.0.9, so the upper bound is violated.

dependencyConvergence, on the other hand, passed in this lab. Even if your project enables both rules, requireUpperBoundDeps is the only one failing. That’s the only rule the fix needs to address.
The Boot 4.0.9 runtime is fine: dependency:tree, jar contents, startup
A failing Enforcer check doesn’t mean 4.1.2 jars leak into the build. I confirmed that three ways.
dependency:tree -Dverbose
With -Dverbose, the tree prints the originally requested version next to each managed one.
$ cd boot40-strict && mvn -B -Dmaven.repo.local=../.tools/m2/repository dependency:tree -Dverbose -Dincludes=org.springframework.boot:spring-boot-starter-restclient,org.springframework.boot:spring-boot-starter-webclient,io.micrometer:micrometer-core
...
[INFO] com.example.aiboot:ai-boot40-strict:jar:0.0.1
[INFO] \- org.springframework.ai:spring-ai-starter-model-ollama:jar:2.0.2:compile
[INFO] +- org.springframework.boot:spring-boot-starter-webclient:jar:4.0.9:compile (version managed from 4.1.2)
[INFO] +- org.springframework.boot:spring-boot-starter-restclient:jar:4.0.9:compile (version managed from 4.1.2)
[INFO] \- org.springframework.ai:spring-ai-ollama:jar:2.0.2:compile (version managed from 2.0.2)
[INFO] \- org.springframework.ai:spring-ai-model:jar:2.0.2:compile (version managed from 2.0.2)
[INFO] \- org.springframework.ai:spring-ai-commons:jar:2.0.2:compile (version managed from 2.0.2)
[INFO] \- io.micrometer:micrometer-core:jar:1.16.8:compile (version managed from 1.17.2)
version managed from 4.1.2 tells the same story as (managed) <-- ...:4.1.2 in the Enforcer error. The selected versions are 4.0.9 and 1.16.8.
Libraries inside the executable jar
I turned off only the Enforcer with -Denforcer.skip=true, ran package, and listed the libraries inside the jar.
$ cd boot40-strict && unzip -l target/ai-boot40-strict-0.0.1.jar | grep -E "BOOT-INF/lib/(spring-boot-[0-9]|spring-boot-restclient|spring-boot-webclient|micrometer-core|micrometer-observation)"
910484 1980-02-01 00:00 BOOT-INF/lib/micrometer-core-1.16.8.jar
83382 1980-02-01 00:00 BOOT-INF/lib/micrometer-observation-1.16.8.jar
1372791 1980-02-01 00:00 BOOT-INF/lib/spring-boot-4.0.9.jar
44672 1980-02-01 00:00 BOOT-INF/lib/spring-boot-restclient-4.0.9.jar
23678 1980-02-01 00:00 BOOT-INF/lib/spring-boot-webclient-4.0.9.jar
None of the listed jars is a 4.1.x or 1.17.x version.
Starting the app
The app prints the Boot version and the jar each class was loaded from, asks Ollama one question, and exits.
@Bean
CommandLineRunner report(ChatClient.Builder chatClientBuilder) {
return args -> {
System.out.println(">>> Spring Boot version: " + SpringBootVersion.getVersion());
System.out.println(">>> RestClientAutoConfiguration from: " + jarOf(RestClientAutoConfiguration.class));
System.out.println(">>> micrometer-core from: " + jarOf(MeterRegistry.class));
String answer = chatClientBuilder.build()
.prompt()
.user("Reply with the single word OK.")
.call()
.content();
System.out.println(">>> Ollama answer: " + answer.strip());
};
}
jarOf() is a small helper that takes the class’s CodeSource location and returns only the jar file name. Here’s the output.
$ cd boot40-strict && java -jar target/ai-boot40-strict-0.0.1.jar
...
>>> Spring Boot version: 4.0.9
>>> RestClientAutoConfiguration from: spring-boot-restclient-4.0.9.jar
>>> micrometer-core from: micrometer-core-1.16.8.jar
>>> Ollama answer: OK
Spring AI 2.0.2 works on Boot 4.0.9. The official Getting Started page also states "Spring AI 2.0.x supports Spring Boot 4.0.x and 4.1.x." So this error isn’t a runtime incompatibility. It’s a clash between POM metadata and an Enforcer rule.
Fix: upgrade to Boot 4.1.2 or add requireUpperBoundDeps excludes
There are two fixes. Which one you pick depends on whether you can change your Boot version.
Fix 1: upgrade Spring Boot to 4.1.2
This is the cleanest option. Change the parent version to 4.1.2, and the requested and managed versions line up. The target is the Boot version the Spring AI POMs request, or newer. Spring AI 2.0.2 requests 4.1.2, so 4.1.0 or 4.1.1 can still trip the same rule.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.2</version>
<relativePath/>
</parent>
With the Enforcer configuration unchanged, both rules pass.
$ cd boot41 && mvn -B -Dmaven.repo.local=../.tools/m2/repository validate
...
[INFO] Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence passed
[INFO] Rule 1: org.apache.maven.enforcer.rules.dependency.RequireUpperBoundDeps passed
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
In dependency:tree, the request and the selection now match, as in (version managed from 4.1.2). At startup the app reported Spring Boot version: 4.1.2 and micrometer-core-1.17.2.jar, and Ollama answered OK.
A Boot minor upgrade affects far more than Spring AI, though. If your organization pins Boot through a platform parent, you may not be able to do this right away. In that case, go with Fix 2.
Fix 2: add excludes to requireUpperBoundDeps
If you have to stay on Boot 4.0.x, remove just the affected artifacts from the rule. requireUpperBoundDeps supports an excludes parameter in groupId[:artifactId] format. I listed the four artifacts from the error as they appear there.
<rules>
<dependencyConvergence/>
<requireUpperBoundDeps>
<excludes>
<exclude>org.springframework.boot:spring-boot-starter-restclient</exclude>
<exclude>org.springframework.boot:spring-boot-starter-webclient</exclude>
<exclude>io.micrometer:micrometer-core</exclude>
<exclude>io.micrometer:micrometer-observation</exclude>
</excludes>
</requireUpperBoundDeps>
</rules>
Now package goes through the Enforcer and succeeds. Each ignored artifact gets one line in the log.
$ cd boot40-excludes && mvn -B -Dmaven.repo.local=../.tools/m2/repository package
...
[INFO] --- enforcer:3.6.3:enforce (enforce-dependencies) @ ai-boot40-excludes ---
[INFO] Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence passed
[INFO] Ignoring requireUpperBoundDeps in org.springframework.boot:spring-boot-starter-restclient
[INFO] Ignoring requireUpperBoundDeps in org.springframework.boot:spring-boot-starter-webclient
[INFO] Ignoring requireUpperBoundDeps in io.micrometer:micrometer-core
[INFO] Ignoring requireUpperBoundDeps in io.micrometer:micrometer-observation
[INFO] Rule 1: org.apache.maven.enforcer.rules.dependency.RequireUpperBoundDeps passed
Running this jar prints Spring Boot version: 4.0.9 and spring-boot-restclient-4.0.9.jar, and Ollama answers OK. The runtime stays on Boot 4.0.9. Only the check changed.
Pitfalls when using excludes
- List exactly the artifacts from the error. If you exclude a whole groupId such as
io.micrometer, you’ll miss real upper-bound violations in other Micrometer modules. - Recheck the list whenever you upgrade Spring AI. This list is for 2.0.2 with the Ollama starter. Adding another starter can bring in other Boot modules that hit the rule.
- Delete the
excludesonce Boot reaches the version the Spring AI POMs request (4.1.2 for 2.0.2) or newer. Left in place, they hide real conflicts that show up later. - Use
-Denforcer.skip=trueonly for diagnosis. It turns off every rule, so all other checks disappear too.
Why the maintainers won’t change this, and how to decide
In issue #6465, the maintainers decided not to change the behavior and not to document it. Their reasoning: the problem only surfaces with the Enforcer rule, so a line in the docs "would bring more confusion than solve practical problems." They also said Spring AI will keep publishing POMs that reference the latest compatible Boot version while still allowing a lower version at runtime. The issue is closed.
Not everyone agreed. A later comment reported a similar problem in Gradle, where some Boot modules moved up to 4.1.0 and others did not. I only tested Maven in this lab. If you use Gradle, check your resolved dependencies yourself.

By situation:
| Situation | Recommendation | Why |
|---|---|---|
| You can change the Boot version in your project | Fix 1 (Boot 4.1.2) | Requested and managed versions match, no exceptions needed |
| A platform parent pins Boot 4.0.x | Fix 2 (excludes) |
Runtime stays on 4.0.x and the check stays on |
| You don’t use the Enforcer | Nothing | The build doesn’t fail and the jar is 4.0.x-based |
The deciding check is simple. Run dependency:tree -Dverbose and see whether the selected versions match your Boot BOM. If they do, the error is a metadata problem and an exclusion is enough. If they don’t, BOM management is missing somewhere, so start by reviewing your dependencyManagement.
Summary
- The Spring AI 2.0.2 POMs request Boot starters 4.1.2 and Micrometer 1.17.2 with explicit versions.
- The Boot 4.0.9 parent lowers them to 4.0.9 and 1.16.8, and
requireUpperBoundDepsflags the downgrade.dependencyConvergencepasses. dependency:tree, the jar contents, and an actual startup were all on Boot 4.0.9, and the Ollama call succeeded.- Upgrading to Boot 4.1.2 passes without exceptions. If you must stay on 4.0.x, add only the four artifacts from the error to
excludes. - The maintainers chose to keep this behavior and not document it, so recheck your
excludeslist every time you upgrade Spring AI.
References
- Spring AI 2.0.0 starters pull Spring Boot 4.1.0 dependencies despite documented Spring Boot 4.0.x compatibility (spring-ai #6465)
- Spring AI Reference: Getting Started
- Apache Maven Enforcer: Require Upper Bound Dependencies
- Apache Maven: Introduction to the Dependency Mechanism
- Maven Central: spring-ai-starter-model-ollama 2.0.2 POM
- Spring AI v2.0.2 release