Skip to content

fix: recognise the nested jars of Spring Boot when looking up the token file - #25681

Merged
mcollovati merged 1 commit into
mainfrom
fix/count-nested-jars-in-token-file-lookup
Sep 14, 2026
Merged

mcollovati merged 1 commit into
mainfrom
fix/count-nested-jars-in-token-file-lookup

Conversation

@totally-not-ai

Copy link
Copy Markdown
Contributor

Follow-up to #6858

behavior deviation · flow-server · applications packaged with
Spring Boot 3.2 or newer, where a dependency also contains a
flow-build-info.json

Background — the token file lookup. The build writes
flow-build-info.json into the build output folder, and the runtime
reads it from the class path at startup. A copy inside a jar is skipped,
unless the application is packaged into a jar itself, in which case its
own file is inside one too and has to be used.

Whether the application is packaged is decided by counting the archives
a resource of flow-server is inside of, and the separator counted for
that is the one Spring Boot used before 3.2. Applications packaged with
a newer Spring Boot therefore never looked packaged, and the file of a
dependency was used whenever the class loader returned it first, giving
the application the settings of the project that built the dependency.

Risks:

  • ⚠️ Behavior change: with several token files inside jars, the one of
    the application is now used instead of the first one found. The old
    order was accidental.
  • ✅ No public API changes, no security, memory, serialization,
    threading or performance impact, nothing to migrate.

Context. Spring Boot 3.2 replaced the nested jar URLs of its own
loader with the nested: protocol, which separates the jar of the
application from an archive inside it with /!, as in
nested:/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts. The
lookup only counted jar!/, so it saw one archive where there are two.

  • Counted both the jar!/ and the jar/! separator when working out
    how many archives a resource is inside of, so an application packaged
    with Spring Boot 3.2 or newer is recognised as packaged and the token
    file of the application is preferred over the one of a dependency.
  • Added a test with the nested jar URLs of Spring Boot, where the file
    of a dependency comes first on the class path.

…en file

The lookup for flow-build-info.json decides that the application is
packaged into a jar by counting the archives the vite.generated.ts of
flow-server is inside of, and then takes the file in the outermost
archive, which is the one of the application itself.

Spring Boot 3.2 and newer separate the jar of the application from an
archive nested in it with '/!' instead of '!/', so nothing was counted
for it and the application never looked packaged. The file of a
dependency was then used whenever the class loader happened to return it
first.

Both separators are now counted, so the file of the application wins
again.
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

Type of change

  • Bugfix

How to test

This cannot be exercised by hand without packaging an application, so it
is covered by
DefaultApplicationConfigurationFactoryTest.create_nestedJarsOfSpringBoot_tokenFileOfTheApplicationIsUsed,
which fails on main and passes here.

Test coverage

The test mocks the class path of an application packaged with Spring
Boot 3.2 or newer:

  • vite.generated.ts of flow-server at
    nested:/opt/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts
  • the token file of a dependency at
    nested:/opt/app.jar/!BOOT-INF/lib/addon.jar!/META-INF/VAADIN/config/flow-build-info.json,
    returned first
  • the token file of the application at
    file:/opt/app.jar!/META-INF/VAADIN/config/flow-build-info.json

The URL formats were taken from spring-boot-loader 4.1.0 rather than
written by hand.

API changes

None. Both changed methods are private.

Note

This touches the same method as the pull request from
fix/ignore-dev-mode-token-file-from-jar, which only uses a token file
from a jar when it comes from a production build. The two are
independent, so whichever is merged second needs a small conflict
resolution: count the archive levels with countArchiveLevels in every
place the other change counts jar!/.

@Artur-

Artur- commented Sep 11, 2026

Copy link
Copy Markdown
Member

@claude review, does this make sense?

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @Artur-'s task in 1m 45s —— View job


Review: token file lookup for Spring Boot 3.2+ nested jars

  • Read the diff and surrounding code
  • Trace the counting logic against the real URL formats
  • Check the test and helper refactoring

Yes, this makes sense — the approach is correct. The bug is real: since Spring Boot 3.2 the nested: protocol separates the application jar from an archive inside it with /! instead of !/, so a lookup that only counted jar!/ under-counted the nesting depth and a packaged app never looked packaged. Counting both separators is the right fix.

Tracing it through (DefaultApplicationConfigurationFactory.java:248-250):

resource path jar!/ jar/! levels
vite.generated.ts (flow-server, nested lib) nested:/opt/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts 1 1 2 → packaged ✅
dependency token file (nested lib) nested:/opt/app.jar/!BOOT-INF/lib/addon.jar!/… 1 1 2 → skipped ✅
application token file file:/opt/app.jar!/… 1 0 1 → accepted ✅

The >= 2 gate and the == 1 filter are unchanged, and the pre-3.2 layout (app.jar!/BOOT-INF/lib/flow-server.jar!/… = two jar!/) still counts as 2, so nothing regresses. The two separators can't overlap or double-count a single boundary, so countArchiveLevels is safe. Extracting it into one named helper (and updating the three call sites + the comment) is a clean refactor.

Minor, non-blocking observations:

  1. Realism of the application token-file URL. The test puts the app's own token file at file:/opt/app.jar!/META-INF/…. In a real Spring Boot 3.2 fat jar the application's flow-build-info.json sits under BOOT-INF/classes, so the runtime URL is closer to nested:/opt/app.jar/!BOOT-INF/classes/!/META-INF/… (and the flow-server resource is usually prefixed jar:nested:…). The good news is the invariant still holds — that path has exactly one jar/! (from app.jar/!; classes/! doesn't contain jar), so it counts as 1 level and is still preferred. It might be worth using the nested: form for the application file too, so the test mirrors production rather than relying on a hand-picked file: URL that happens to yield the same count.

  2. Substring counting is layout-dependent by design. The whole mechanism keys off literal jar!/ / jar/! markers in the path, so it's inherently coupled to loader URL conventions (this PR is itself the third such marker). That's pre-existing and fine for a targeted fix — just flagging that a future loader change (or an addon shaded as something.zip) would need the same treatment. No action needed here.

  3. Rebase note from the PR body is worth honouring: whichever of this and fix/ignore-dev-mode-token-file-from-jar lands second should route its jar!/ counts through countArchiveLevels too, so the two fixes compose.

The helper Javadoc and the concrete nested: example in it are a nice touch. LGTM. 👍
· branch fix/count-nested-jars-in-token-file-lookup

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 1 440 files  ±0   1 524 suites  ±0   1h 35m 27s ⏱️ +50s
12 061 tests +1  11 993 ✅ +1  68 💤 ±0  0 ❌ ±0 
12 379 runs  +1  12 311 ✅ +1  68 💤 ±0  0 ❌ ±0 

Results for commit c24ed0c. ± Comparison against base commit 980ac5b.

@mcollovati
mcollovati added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit bbf4fb4 Sep 14, 2026
68 of 69 checks passed
@mcollovati
mcollovati deleted the fix/count-nested-jars-in-token-file-lookup branch September 14, 2026 07:56
vaadin-bot added a commit that referenced this pull request Sep 14, 2026
…en file (#25681) (CP: 25.2) (#25696)

This PR cherry-picks changes from the original PR #25681 to branch 25.2.
---
#### Original PR description
> Follow-up to #6858
> 
> **behavior deviation** · flow-server · applications packaged with
> Spring Boot 3.2 or newer, where a dependency also contains a
> flow-build-info.json
> 
> **Background — the token file lookup.** The build writes
> `flow-build-info.json` into the build output folder, and the runtime
> reads it from the class path at startup. A copy inside a jar is
skipped,
> unless the application is packaged into a jar itself, in which case
its
> own file is inside one too and has to be used.
> 
> Whether the application is packaged is decided by counting the
archives
> a resource of `flow-server` is inside of, and the separator counted
for
> that is the one Spring Boot used before 3.2. Applications packaged
with
> a newer Spring Boot therefore never looked packaged, and the file of a
> dependency was used whenever the class loader returned it first,
giving
> the application the settings of the project that built the dependency.
> 
> **Risks:**
> - ⚠️ Behavior change: with several token files inside jars, the one of
>   the application is now used instead of the first one found. The old
>   order was accidental.
> - ✅ No public API changes, no security, memory, serialization,
>   threading or performance impact, nothing to migrate.
> 
> **Context.** Spring Boot 3.2 replaced the nested jar URLs of its own
> loader with the `nested:` protocol, which separates the jar of the
> application from an archive inside it with `/!`, as in
> `nested:/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts`.
The
> lookup only counted `jar!/`, so it saw one archive where there are
two.
> 
> - Counted both the `jar!/` and the `jar/!` separator when working out
> how many archives a resource is inside of, so an application packaged
> with Spring Boot 3.2 or newer is recognised as packaged and the token
>   file of the application is preferred over the one of a dependency.
> - Added a test with the nested jar URLs of Spring Boot, where the file
>   of a dependency comes first on the class path.
>

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
vaadin-bot added a commit that referenced this pull request Sep 14, 2026
…en file (#25681) (CP: 25.3) (#25695)

This PR cherry-picks changes from the original PR #25681 to branch 25.3.
---
#### Original PR description
> Follow-up to #6858
> 
> **behavior deviation** · flow-server · applications packaged with
> Spring Boot 3.2 or newer, where a dependency also contains a
> flow-build-info.json
> 
> **Background — the token file lookup.** The build writes
> `flow-build-info.json` into the build output folder, and the runtime
> reads it from the class path at startup. A copy inside a jar is
skipped,
> unless the application is packaged into a jar itself, in which case
its
> own file is inside one too and has to be used.
> 
> Whether the application is packaged is decided by counting the
archives
> a resource of `flow-server` is inside of, and the separator counted
for
> that is the one Spring Boot used before 3.2. Applications packaged
with
> a newer Spring Boot therefore never looked packaged, and the file of a
> dependency was used whenever the class loader returned it first,
giving
> the application the settings of the project that built the dependency.
> 
> **Risks:**
> - ⚠️ Behavior change: with several token files inside jars, the one of
>   the application is now used instead of the first one found. The old
>   order was accidental.
> - ✅ No public API changes, no security, memory, serialization,
>   threading or performance impact, nothing to migrate.
> 
> **Context.** Spring Boot 3.2 replaced the nested jar URLs of its own
> loader with the `nested:` protocol, which separates the jar of the
> application from an archive inside it with `/!`, as in
> `nested:/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts`.
The
> lookup only counted `jar!/`, so it saw one archive where there are
two.
> 
> - Counted both the `jar!/` and the `jar/!` separator when working out
> how many archives a resource is inside of, so an application packaged
> with Spring Boot 3.2 or newer is recognised as packaged and the token
>   file of the application is preferred over the one of a dependency.
> - Added a test with the nested jar URLs of Spring Boot, where the file
>   of a dependency comes first on the class path.
>

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
vaadin-bot added a commit that referenced this pull request Sep 14, 2026
…en file (#25681) (CP: 25.1) (#25697)

This PR cherry-picks changes from the original PR #25681 to branch 25.1.
---
#### Original PR description
> Follow-up to #6858
> 
> **behavior deviation** · flow-server · applications packaged with
> Spring Boot 3.2 or newer, where a dependency also contains a
> flow-build-info.json
> 
> **Background — the token file lookup.** The build writes
> `flow-build-info.json` into the build output folder, and the runtime
> reads it from the class path at startup. A copy inside a jar is
skipped,
> unless the application is packaged into a jar itself, in which case
its
> own file is inside one too and has to be used.
> 
> Whether the application is packaged is decided by counting the
archives
> a resource of `flow-server` is inside of, and the separator counted
for
> that is the one Spring Boot used before 3.2. Applications packaged
with
> a newer Spring Boot therefore never looked packaged, and the file of a
> dependency was used whenever the class loader returned it first,
giving
> the application the settings of the project that built the dependency.
> 
> **Risks:**
> - ⚠️ Behavior change: with several token files inside jars, the one of
>   the application is now used instead of the first one found. The old
>   order was accidental.
> - ✅ No public API changes, no security, memory, serialization,
>   threading or performance impact, nothing to migrate.
> 
> **Context.** Spring Boot 3.2 replaced the nested jar URLs of its own
> loader with the `nested:` protocol, which separates the jar of the
> application from an archive inside it with `/!`, as in
> `nested:/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts`.
The
> lookup only counted `jar!/`, so it saw one archive where there are
two.
> 
> - Counted both the `jar!/` and the `jar/!` separator when working out
> how many archives a resource is inside of, so an application packaged
> with Spring Boot 3.2 or newer is recognised as packaged and the token
>   file of the application is preferred over the one of a dependency.
> - Added a test with the nested jar URLs of Spring Boot, where the file
>   of a dependency comes first on the class path.
>

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants