Spring AI 2.0 on Spring Boot 4.0: requireUpperBoundDeps error fix

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-core 1.17.2 with explicit versions. The Boot 4.0.9 parent manages them down to 4.0.9 and 1.16.8, and requireUpperBoundDeps treats 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’s excludes.

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 excludes once 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=true only 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 requireUpperBoundDeps flags the downgrade. dependencyConvergence passes.
  • 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 excludes list every time you upgrade Spring AI.

References

Leave a Comment