Skip to content

fix: ignore a token file in a jar that was written for another project - #25682

Draft
totally-not-ai[bot] wants to merge 9 commits into
mainfrom
fix/ignore-dev-mode-token-file-from-jar
Draft

totally-not-ai[bot] wants to merge 9 commits into
mainfrom
fix/ignore-dev-mode-token-file-from-jar

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Sometimes a dependency ships a flow-build-info.json from a development build by mistake. The application then picked up that file's project folder and Node version, and startup either failed or ran the frontend build in the wrong project. Now a token file from a jar is used only when it is from a production build, or when it was written for the project the application runs from.

What changed

Behavior change: these rules apply only when the lookup falls back to a flow-build-info.json inside a jar. This happens when the application has no token file of its own, as with a packaged application.

  • A file that declares production mode is always used. Nothing changes for normal packaged apps.
  • A file from a development build is used only if:
    • the project folder it names exists on this machine, and
    • that folder is the application's own project. The project is taken from the class path, which must match exactly. If the class path does not tell, the working directory is used, and any project inside it is accepted. This covers java -jar app/target/app.jar started from the root of a multi-module build.
  • Other files are skipped with a warning. The warning names the ignored file and the reason, so the mistake in the dependency can be fixed.
  • A file that is not valid JSON is skipped.
  • When the app runs from a jar, candidates are checked from the outermost jar first. The warning "Unable to fully determine correct flow-build-info" is now also shown when a jar file was picked among several candidates, unless it is the file in the outermost jar.
  • Who is affected: apps with no token file outside a jar, where a dependency packages a development mode token file. An app packaged in development mode and run on the machine that built it keeps working.
  • Internal: the check "the working directory is a Maven/Gradle project" moved from AbstractConfiguration to FileIOUtils, so both places share it.

API Changes

com.vaadin.flow.internal.FileIOUtils

// Added
public static File getProjectFolderFromWorkingDirectory() // working dir if it has pom.xml / build.gradle(.kts), else null; moved from AbstractConfiguration

Test summary

Token file lookup inside a jar:

  • Production mode file always used, even if a development mode file is found first
  • Development mode file ignored when its project is missing, belongs to another project, or belongs to a dependency built on this machine
  • Development mode file used when it matches the class path project, or is inside the working directory project
  • Working directory used only when it is a Maven/Gradle project

Packaged application:

  • Uses the application's own file when jars are nested
  • Skips a file that cannot be read as JSON

When no flow-build-info.json is found outside a jar, the lookup falls
back to a copy inside one. A copy packaged into a dependency by mistake
is written by prepare-frontend, so it carries the folders and the Node
version of the machine that built the dependency, and the application
fails to start with a folder it has never heard of.

Adds a failing test for that case and one that keeps the packaged
application case, where the token file of the application itself is
inside a jar, working.
The flow-build-info.json lookup skips copies inside jars and falls back
to one only when the application has none of its own, which is the case
for a packaged application. A copy that a dependency packages by mistake
was then used as well, and as prepare-frontend writes it, it brought the
project folders and the Node version of the machine that built the
dependency, failing the startup with a folder that does not exist.

A file from a jar is now used only when it declares production mode,
which a packaged application always does and a file left over from a
development build never does. The ignored file is named in a warning so
that the mistake in the dependency can be fixed.
The warning about not being able to tell which flow-build-info.json is
the right one was left out whenever the application looked packaged into
a jar, even when the file was not the one the rule for a packaged
application points at, which is when knowing about the other candidates
helps the most.

The warning is now left out only when the file is the one in the
outermost jar, and the cases with more than one file inside a jar are
covered by tests.
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

Type of change

  • Bugfix

How to test

  1. Build any Vaadin application in development mode, so that
    target/classes/META-INF/VAADIN/config/flow-build-info.json is
    written.
  2. Package that file into a jar of its own,
    jar cf addon.jar -C target/classes META-INF, and add the jar to the
    class path of a second application.
  3. Delete target/classes/META-INF/VAADIN/config/flow-build-info.json
    of the second application, so that only the copy in the jar is left,
    and start it.
  4. Before: the startup fails with Running project in development mode with no access to folder …, naming the folder of the first
    application. After: the file is ignored with a warning that names it,
    and the application starts.
Test coverage

In DefaultApplicationConfigurationFactoryTest:

  • create_onlyDevelopmentModeTokenFileInsideJar_tokenFileIsIgnored — a
    file written by prepare-frontend and packaged into a dependency is
    ignored instead of failing the startup.
  • create_productionModeTokenFileInsideJar_tokenFileIsUsed — the file
    of a packaged application is still used.
  • create_developmentModeTokenFileInsideJarIsFoundFirst_productionModeOneIsUsed
    — a development mode file returned first does not hide the production
    one.
  • create_packagedApplicationWithNestedJars_tokenFileOfTheApplicationIsUsed
    — with nested jars, the file of the application wins over the one of a
    dependency.
  • create_unparseableTokenFileInsideJar_tokenFileIsIgnored — content
    that is not JSON is ignored.

The tests that read the file from the class path now use a plain class
path URL instead of a jar one, which is what they were about.

API changes

None. getTokenFileFromClassloader keeps its signature; the new
isProductionModeTokenFile is private.

Note

#25681 touches the same method, and makes the counting of nested
archives understand the jars of Spring Boot 3.2 and newer. The two are
independent, so whichever is merged second needs a small conflict
resolution: count the archive levels with countArchiveLevels in
every place this change counts jar!/.

@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

 1 551 files   - 15   1 635 suites   - 15   1h 41m 19s ⏱️ -52s
12 477 tests +11  12 405 ✅ +11  72 💤 ±0  0 ❌ ±0 
12 876 runs   - 35  12 804 ✅  - 35  72 💤 ±0  0 ❌ ±0 

Results for commit 9e934f4. ± Comparison against base commit e74ad96.

♻️ This comment has been updated with latest results.

…oken-file-from-jar

# Conflicts:
#	flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java
#	flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java
@mshabarov

Copy link
Copy Markdown
Contributor

To be verified against use cases on Vaadin Forum where the JARs are build in dev mode, could be valid uses cases (before making this ready for review).

Refusing every token file inside a jar that is not from a production
build also refused the file of an application that is packaged into a
jar in development mode and run on the machine it was built on, where
the project it points to is still there and the settings in it are the
right ones. Only a plain or shaded jar is affected, as the file of an
application packaged with Spring Boot is under BOOT-INF/classes and is
never looked for inside a jar to begin with.

A file that is not from a production build is now used when the project
it names is on this machine, and ignored when it is not, which is the
case for a file packaged into a dependency built somewhere else. The
warning names the project folder that is missing.
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

@mshabarov Good call — there is a valid case, and the rule is now narrower.

What I checked first is which packagings even reach this code. The fallback only runs when every flow-build-info.json on the class path is inside a jar, and a candidate is only treated as "inside a jar" when its path ends with jar!/META-INF/VAADIN/config/flow-build-info.json. Taking the URL formats from spring-boot-loader 4.1.0 rather than guessing:

  • Spring Boot application, own file: file:/opt/app.jar!/BOOT-INF/classes/META-INF/VAADIN/config/flow-build-info.json
  • Spring Boot, file of a dependency: nested:/opt/app.jar/!BOOT-INF/lib/addon.jar!/META-INF/VAADIN/config/flow-build-info.json
  • plain or shaded jar, own file: file:/opt/app.jar!/META-INF/VAADIN/config/flow-build-info.json

So an application packaged with Spring Boot, in development mode or not, never gets here: its own file is under BOOT-INF/classes and is picked before the fallback. What does get here is a plain or shaded jar — and packaging one in development mode and running it on the machine that built it is a valid use case, because the project the file points to is still there and its settings are the right ones. The first version of this change would have broken exactly that.

The rule is now:

  • a file from a production build is used, as before — a packaged application always writes productionMode: true and no folders;
  • a file that is not from a production build is used when the project folder it names exists on this machine;
  • it is ignored when that folder does not exist, which is the case for a file packaged into a dependency built somewhere else. That is the broken case: today it is applied and the startup fails with Running project in development mode with no access to folder …, naming a folder from another machine.

The warning now names the missing project folder, so the dependency at fault can be found. Added create_developmentModeTokenFileInsideJarForThisProject_tokenFileIsUsed for the development mode jar that keeps working, and renamed the existing test to create_developmentModeTokenFileInsideJarForAnotherProject_tokenFileIsIgnored, which is what it really covers.

@Artur-

Artur- commented Sep 22, 2026

Copy link
Copy Markdown
Member

Instead of doing it this way, could we do a refactoring so that development time info and production mode info would be written to separate files and the production mode info would be read from a jar? So instead of flow-build-info we would have flow-development-mode-info and flow-production-mode-info and it would be clear what is what

@totally-not-ai

Copy link
Copy Markdown
Contributor Author

@Artur- I agree that is the better model, and it would remove a whole family of workarounds rather than just this one. I wrote it up as #25856 instead of turning it into this pull request, because it reaches further than it looks.

What made me keep the two apart:

  • The split touches both build plugins, the runtime, flow-devloop-daemon and the docs, and it needs a compatibility path: an artifact built by an older version carries only flow-build-info.json, so the runtime has to keep reading that file with content-based rules for at least one major. That rule is what this pull request adds, so it is needed either way during the transition.
  • The split does not decide the case this pull request ran into last: an application packaged as a plain or shaded jar in development mode has its development mode file inside a jar too, so something still has to say whether that file belongs to the application being run. Either we keep reading it from a jar when the project folder it names exists on this machine, which is the rule here, or we declare that packaging unsupported in development mode. Worth deciding explicitly rather than as a side effect of the rename.

What the write-up also collects is how much the single file already costs us, which is the strongest argument for doing it: the runtime guesses which jar owns a copy by counting jar!/ separators; add-ons have to exclude the file from their artifact by hand, as our own flow-tests/test-express-build/java-add-on/pom.xml does; the Maven plugin deletes the file after a production build so that a later run from an IDE does not read a stale productionMode: true token; and the Gradle plugin keeps the production tree in a separate output directory to avoid a second, conflicting copy in the archive. Two clearly named files make all of that unnecessary.

So my suggestion is to land this as the fix for the broken startups, and do the split on its own in #25856. If you would rather have the split first and let this wait, that is fine too — say so and I will leave this pull request as is until then.

@samie

samie commented Sep 28, 2026

Copy link
Copy Markdown
Member

Runtime (Copilot preparing frontend) and compile time mvn vaadin:prepare-frontend behavior are inconsistent. I have a case where an old add-on pulls in old Node version at runtime in dev mode and with Maven plugin frontend build works to find the the correct latest one.

In development mode the project folder of the application is found from
the class path or the working directory, but a token file taken from a
jar overrides it together with the Node version. A token file that an
add-on built on this machine packages names a folder that exists, so it
was used: the application then ran the frontend build with the Node
version and, worse, the project folder of the add-on, while running the
same build through the Maven plugin found the right ones.

A development mode token file from a jar is now used only when the
project it names is the project the application is run from, whenever
that can be told. When it cannot, the file is used if its project is on
this machine, so an application packaged in development mode keeps
working.

The check of the working directory moves to FileIOUtils so the
configuration and the lookup share it.
…n a jar

The working directory was taken as the project folder of the application
as firmly as the class path, so an application packaged in development
mode and started from the root of a multi-module build, as in
java -jar app/target/app.jar, had its own token file ignored: the root
has a pom.xml but is not the module the file was written for.

The class path still has to name exactly the project of the file, as the
application then runs from the output folder of that project. The
working directory is only a hint, so a project inside it is accepted.
The lookups are now tested through the real working directory as well.
@totally-not-ai totally-not-ai Bot changed the title fix: only use a token file from a jar when it is from a production build fix: ignore a token file in a jar that was written for another project Sep 28, 2026
@totally-not-ai

Copy link
Copy Markdown
Contributor Author

@samie Thanks, that case was not covered yet, and it is fixed now.

Why the two disagree: the Maven plugin takes the Node version from its own configuration and never reads a token file, while the runtime in development mode reads flow-build-info.json from the class path. Since Vaadin 25 prepare-frontend no longer runs on every build in development mode, so the application often has no token file of its own, and the lookup falls back to a copy inside a jar — here the one the old add-on packages. That copy then overrides both the Node version and the project folder the runtime would otherwise find from the class path.

The previous rule on this pull request only ignored such a file when the project folder it names did not exist. An add-on built on the same machine names a folder that does exist, so it slipped through — which matches what you saw.

Changed now: a development mode token file from a jar is used only when it was written for the application being run. When the application runs from the output folder of its project (target/classes, or the Gradle output folders), the file has to name exactly that project. Otherwise, when the working directory is a Maven or Gradle project, the file has to name that folder or a module inside it, so java -jar app/target/app.jar started from the root of a multi-module build keeps working. When neither can be told, the file is still used if its project is on this machine, so an application packaged as a plain or shaded jar in development mode keeps working. Production mode files are unaffected.

The warning names both folders, for example: Ignoring the file '…/addon.jar!/META-INF/VAADIN/config/flow-build-info.json' found inside a jar, as it is not from a production build and was written for the project in '/home/me/my-addon', not for the application being run from '/home/me/my-app', so it is packaged into a dependency by mistake.

Covered by create_developmentModeTokenFileInsideJarOfDependencyBuiltOnThisMachine_tokenFileIsIgnored, which fails without the change. For the add-on itself, excluding META-INF/VAADIN/config/flow-build-info.json from its jar remains the proper fix.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants