chore(server): fix maven caching for graphql autogenerated classes - #948
Conversation
The quick profile skipped GraphQL codegen but not the clean-stale-classes execution, which deleted all .class files every build — forcing full recompilation of thousands of generated files (~30s penalty). Changes: - Add skipCleanStaleClasses=true to quick profile for incremental builds - Change activation from target/classes to target/generated-sources (target/classes can be an empty dir after cleanup, causing false activation) - Upgrade maven-compiler-plugin 3.14.1→3.15.0 (fixes MCOMPILER-540) - Include .graphql/.ftl files in CI cache key for correctness Closes #945 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughMaven caching for GraphQL autogenerated classes is fixed by updating cache keys and profile activation conditions across GitHub Actions workflows and project configuration to use Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes incremental build performance for the application server by preventing unnecessary recompilation of thousands of GraphQL-generated Java classes, and by improving CI cache correctness.
Changes:
- Adjust the
quickMaven profile to skip stale-class cleanup (in addition to skipping GraphQL codegen) to preserve compiled.classfiles for incremental builds. - Change
quickprofile auto-activation to usetarget/generated-sources/graphql-githubrather thantarget/classes. - Update CI Maven cache key to include GraphQL schema/template inputs and bump
maven-compiler-pluginto address generated-sources behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
server/application-server/pom.xml |
Updates quick profile behavior/activation and overrides compiler plugin version; refreshes related comments. |
.github/workflows/ci-tests.yml |
Updates documentation comment about when quick profile auto-activates in CI. |
.github/actions/setup-caches/action.yml |
Expands Maven cache key inputs to include .graphql and .ftl files to avoid stale generated sources. |
| <failsafe.includedGroups>integration</failsafe.includedGroups> | ||
|
|
||
| <!-- GraphQL Codegen Skip (auto-set by 'quick' profile when target/classes exists) --> | ||
| <!-- GraphQL Codegen Skip (auto-set by 'quick' profile when target/generated-sources exists) --> |
There was a problem hiding this comment.
The comment says the quick profile auto-activates when target/generated-sources exists, but the actual activation checks for target/generated-sources/graphql-github. Please update this comment to match the real activation trigger (or broaden the activation to match the comment) so developers don’t get misled when debugging profile activation.
| <!-- GraphQL Codegen Skip (auto-set by 'quick' profile when target/generated-sources exists) --> | |
| <!-- GraphQL Codegen Skip (auto-set by 'quick' profile when target/generated-sources/graphql-github exists) --> |
| <activation> | ||
| <!-- Auto-activate when compiled classes exist --> | ||
| <!-- Uses generated-sources dir, not target/classes, because <exists> matches | ||
| empty dirs and clean-stale-classes can leave target/classes empty. --> | ||
| <file> | ||
| <exists>${basedir}/target/classes</exists> | ||
| <exists>${basedir}/target/generated-sources/graphql-github</exists> | ||
| </file> | ||
| </activation> | ||
| <properties> | ||
| <!-- Skip GraphQL codegen - regenerated sources already exist | ||
| ⚠️ Run 'mvn clean' when schema files change! --> | ||
| <graphql.codegen.skip>true</graphql.codegen.skip> | ||
| <!-- Keep Java compilation enabled for incremental development --> | ||
| <!-- maven.main.skip deliberately NOT set - we want incremental compile --> | ||
| <!-- Skip test compilation for faster spring-boot:run --> | ||
| <!-- Preserve .class files for incremental compilation --> | ||
| <skipCleanStaleClasses>true</skipCleanStaleClasses> | ||
| <!-- maven.main.skip deliberately NOT set — we want incremental compile --> | ||
| <maven.test.skip>true</maven.test.skip> |
There was a problem hiding this comment.
With activation now based on target/generated-sources/graphql-github, the quick profile will also auto-activate in CI whenever the workflow cache restores generated sources. That makes skipCleanStaleClasses=true and graphql.codegen.skip=true in CI, which can undermine the purpose of the clean-stale-classes execution (especially since the Maven Build Cache config attaches target/classes/test-classes to cached outputs). Consider preventing quick from activating on CI (e.g., add an activation guard like requiring !env.CI), or explicitly disable the profile / override skipCleanStaleClasses in CI so stale compiled classes cannot slip through.
| # -Dmaven.test.skip=false overrides the quick profile (auto-activated when target/generated-sources exists from cache) | ||
| case "${{ matrix.test-type }}" in | ||
| "application-server-unit-arch") ./mvnw test -Dsurefire.includedGroups="unit | architecture" -DskipAfterFailureCount=10 -Dmaven.test.skip=false -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; | ||
| "application-server-integration") ./mvnw test -Dsurefire.includedGroups="integration" -Dparallel=none -Dhephaestus.surefire.timeout=600 -Dmaven.test.skip=false -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; |
There was a problem hiding this comment.
This comment is inaccurate: -Dmaven.test.skip=false does not “override the quick profile”; it only overrides the maven.test.skip property value. If the quick profile activates (which it will when the cached generated-sources directory is restored), its other effects still apply (e.g., skipping GraphQL codegen / stale-class cleanup). Please reword the comment to reflect what is actually being overridden, and consider explicitly disabling the quick profile in CI if those other effects are not desired.
| # -Dmaven.test.skip=false overrides the quick profile (auto-activated when target/generated-sources exists from cache) | |
| case "${{ matrix.test-type }}" in | |
| "application-server-unit-arch") ./mvnw test -Dsurefire.includedGroups="unit | architecture" -DskipAfterFailureCount=10 -Dmaven.test.skip=false -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; | |
| "application-server-integration") ./mvnw test -Dsurefire.includedGroups="integration" -Dparallel=none -Dhephaestus.surefire.timeout=600 -Dmaven.test.skip=false -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; | |
| # -Dmaven.test.skip=false overrides the maven.test.skip property so tests run even if the quick profile would skip them | |
| # The quick profile itself is explicitly disabled in CI via -P '!quick' to avoid its other effects when target/generated-sources exists from cache | |
| case "${{ matrix.test-type }}" in | |
| "application-server-unit-arch") ./mvnw test -Dsurefire.includedGroups="unit | architecture" -DskipAfterFailureCount=10 -Dmaven.test.skip=false -P '!quick' -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; | |
| "application-server-integration") ./mvnw test -Dsurefire.includedGroups="integration" -Dparallel=none -Dhephaestus.surefire.timeout=600 -Dmaven.test.skip=false -P '!quick' -T 2C --batch-mode -Dmaven.build.cache.enabled=true ;; |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/actions/setup-caches/action.yml:
- Around line 55-57: The fallback cache allows restoring stale generated sources
because restore-keys on the maven cache can bring back
server/application-server/target/generated-sources/graphql-github and cause the
"quick" profile in server/application-server/pom.xml to skip regeneration; to
fix, remove the non-exact restore-keys for the maven key (the "key:" and
"restore-keys:" entries) so the cache is exact-only, or split caches so that
target/** (generated-sources) is cached only with exact keys while
~/.m2/repository is a separate cache with restore-keys — adjust the action.yml
entries accordingly to ensure only ~/.m2/repository is eligible for fallback and
target/generated-sources is exact-match only.
In `@server/application-server/pom.xml`:
- Around line 1084-1085: The profile activation sentinel currently uses
target/generated-sources/graphql-github which is evaluated only at Maven startup
and can remain true after a subsequent clean, so update the pom/documentation:
change the activation sentinel for the quick profile from
target/generated-sources/graphql-github to a stable file outside target (for
example ${basedir}/.maven-generated-graphql) or revise the recovery guidance to
use a guaranteed-profile-disable or full target removal (e.g., replace the "Run
'mvn clean compile'..." guidance with "rm -rf target && mvn compile" or "mvn
clean compile -P!quick") and apply the same change to the other occurrence noted
in the same profile block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 95e46f88-c273-44f3-9060-2e8b9146da59
📒 Files selected for processing (3)
.github/actions/setup-caches/action.yml.github/workflows/ci-tests.ymlserver/application-server/pom.xml
| key: ${{ inputs.os }}-maven-${{ hashFiles('server/application-server/pom.xml', 'server/application-server/src/main/resources/graphql/**/*.graphql', 'server/application-server/src/main/resources/graphql/**/*.ftl') || 'no-pom' }} | ||
| restore-keys: | | ||
| ${{ inputs.os }}-maven- |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
How does GitHub Actions actions/cachebehave when the primarykeymisses but arestore-keysprefix matches? Can it restore an older cache while reportingcache-hit as false?
💡 Result:
Yes, GitHub Actions actions/cache restores an older cache when the primary key does not exactly match any existing cache but a restore-keys prefix matches an existing cache (selecting the most recent one). In this case, it reports cache-hit as false (specifically, the output is the string 'false' or empty on miss, but 'false' when restored via restore-keys). The matching process is: 1. Search for exact match on key: if found, restore and cache-hit: 'true'. 2. Search for prefix matches on key: if found (most recent), restore but cache-hit not 'true' (treated as miss for exactness). 3. If no match for key, sequentially search restore-keys for prefix matches: if found (most recent for that prefix), restore but cache-hit: 'false'. This behavior is confirmed across official documentation, action READMEs, and GitHub issues/discussions as of 2026. Workflows typically check steps.cache.outputs.cache-hit != 'true' to run dependency installation even on partial/restore restores, as the restored cache may be stale.
Citations:
- 1: Wrong Cache restore operation actions/cache#1433
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#:~:text=The cache action first searches,restore-keys and the version.
- 3: https://stackoverflow.com/questions/72752961/github-actions-why-cache-restored-successfully-but-cache-hit-got-false-pr
- 4: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching
- 5: https://github.com/actions/cache
- 6: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows
- 7: https://git.edu.yar.ru/actions/cache/src/branch/main/restore/README.md
- 8: https://github.com/marketplace/actions/cache
- 9: https://git.d.aiengines.ir/actions/cache/src/commit/5a1720c49e91718f5d0ce7f154997c93e0f6159a/restore/README.md?display=source
- 10: https://git.edu.yar.ru/actions/cache/src/tag/v5/restore/README.md
🏁 Script executed:
# Find the pom.xml file
fd pom.xml server/application-serverRepository: ls1intum/Hephaestus
Length of output: 96
🏁 Script executed:
# Check the size of pom.xml to decide how to read it
wc -l server/application-server/pom.xmlRepository: ls1intum/Hephaestus
Length of output: 101
🏁 Script executed:
# Search for the `quick` profile in pom.xml
rg -A 5 -B 2 "quick" server/application-server/pom.xmlRepository: ls1intum/Hephaestus
Length of output: 1928
🏁 Script executed:
# Verify the full action.yml to see the cache configuration context
cat -n .github/actions/setup-caches/action.ymlRepository: ls1intum/Hephaestus
Length of output: 5882
The fallback cache can still rehydrate stale generated sources.
Line 55 protects exact cache hits only. When a .graphql or .ftl file changes, the primary key misses, but lines 56-57 can restore an older ${{ inputs.os }}-maven-... entry containing stale server/application-server/target/generated-sources/graphql-github. This triggers the quick profile in server/application-server/pom.xml (which activates on directory existence), skipping GraphQL code generation even though the schema changed. Either make this cache exact-key only by removing restore-keys, or split ~/.m2/repository into a separate fallback cache while keeping target/** on exact matches only.
Proposed fix
with:
path: |
~/.m2/repository
server/application-server/target/generated-sources
server/application-server/target/maven-status
key: ${{ inputs.os }}-maven-${{ hashFiles('server/application-server/pom.xml', 'server/application-server/src/main/resources/graphql/**/*.graphql', 'server/application-server/src/main/resources/graphql/**/*.ftl') || 'no-pom' }}
- restore-keys: |
- ${{ inputs.os }}-maven-📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| key: ${{ inputs.os }}-maven-${{ hashFiles('server/application-server/pom.xml', 'server/application-server/src/main/resources/graphql/**/*.graphql', 'server/application-server/src/main/resources/graphql/**/*.ftl') || 'no-pom' }} | |
| restore-keys: | | |
| ${{ inputs.os }}-maven- | |
| key: ${{ inputs.os }}-maven-${{ hashFiles('server/application-server/pom.xml', 'server/application-server/src/main/resources/graphql/**/*.graphql', 'server/application-server/src/main/resources/graphql/**/*.ftl') || 'no-pom' }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/actions/setup-caches/action.yml around lines 55 - 57, The fallback
cache allows restoring stale generated sources because restore-keys on the maven
cache can bring back
server/application-server/target/generated-sources/graphql-github and cause the
"quick" profile in server/application-server/pom.xml to skip regeneration; to
fix, remove the non-exact restore-keys for the maven key (the "key:" and
"restore-keys:" entries) so the cache is exact-only, or split caches so that
target/** (generated-sources) is cached only with exact keys while
~/.m2/repository is a separate cache with restore-keys — adjust the action.yml
entries accordingly to ensure only ~/.m2/repository is eligible for fallback and
target/generated-sources is exact-match only.
| Run 'mvn clean compile' after modifying .graphql schemas or deleting source files. | ||
| Override: mvn compile -Dgraphql.codegen.skip=false | mvn compile -P!quick |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Maven, when are profile activation <file><exists>...</exists></file> checks evaluated relative to a mvn clean compileinvocation? If the file exists before the build starts, does the profile remain active even though thecleanphase later deletestarget/?
💡 Result:
Maven profile activation using is evaluated very early in the build process, during the Maven Model Builder phase before any lifecycle phases (like clean or compile) are executed. Specifically, it occurs during profile activation in phase 1 of model building, prior to model interpolation, inheritance assembly, and execution of any plugins or lifecycle goals. For a mvn clean compile invocation, which executes the clean lifecycle (deleting target/) followed by default lifecycle up to compile: - Profiles are activated based on the filesystem state at Maven startup, before clean runs. - If the file (e.g., in target/) exists before the build starts, the profile activates and remains active throughout the entire build, including after clean deletes target/. The effective POM, incorporating active profiles, is computed once at the beginning and used for all subsequent lifecycle execution. Profile activation is not re-evaluated during lifecycle phases.
Citations:
- 1: https://maven.apache.org/ref/current/maven-model-builder/
- 2: http://maven.apache.org/ref/3.9.11/maven-model-builder/
- 3: https://maven.apache.org/guides/introduction/introduction-to-profiles.html
- 4: https://stackoverflow.com/questions/28243131/when-is-maven-profile-activation-evaluated
mvn clean compile is an unsafe recovery path with the current profile activation sentinel.
Maven profile activation checks ... only once at startup, before any lifecycle phases execute. With target/generated-sources/graphql-github as the activation sentinel on Line 1093, if this file exists before the build starts, the quick profile activates and remains active throughout the entire build—including after the clean phase deletes target/. This means the GraphQL codegen skip directive on Lines 1097–1099 stays in effect for the compile phase, defeating the recovery guidance on Lines 1084–1085.
Recommend either: move the sentinel outside target/ (e.g., ${basedir}/.maven-generated-graphql with manual cleanup), or update the documented recovery path to rm -rf target && mvn compile or mvn clean compile -P!quick (force-disable the quick profile).
Also applies to: 1090–1100
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/application-server/pom.xml` around lines 1084 - 1085, The profile
activation sentinel currently uses target/generated-sources/graphql-github which
is evaluated only at Maven startup and can remain true after a subsequent clean,
so update the pom/documentation: change the activation sentinel for the quick
profile from target/generated-sources/graphql-github to a stable file outside
target (for example ${basedir}/.maven-generated-graphql) or revise the recovery
guidance to use a guaranteed-profile-disable or full target removal (e.g.,
replace the "Run 'mvn clean compile'..." guidance with "rm -rf target && mvn
compile" or "mvn clean compile -P!quick") and apply the same change to the other
occurrence noted in the same profile block.
📚 Documentation Preview
|
|
🎉 This PR is included in version 0.52.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Description
Fixes #945
On every
spring-boot:run/mvn compile, Maven recompiled all ~3700 autogenerated GraphQL Java classes even when schemas hadn't changed. Root cause: thequickprofile skipped GraphQL codegen but not theclean-stale-classesexecution, which deleted all.classfiles intarget/classesevery build — forcing full recompilation (~30s penalty).Changes:
skipCleanStaleClasses=truetoquickprofile.classfiles souseIncrementalCompilation=false(MCOMPILER-209 workaround) can actually skip unchanged filestarget/classestotarget/generated-sources/graphql-github<exists>matches empty directories;clean-stale-classescan leavetarget/classesempty, causing false activationuseIncrementalCompilation=false.graphql/.ftlin CI Maven cache keypom.xmldoesn'tci-tests.ymlandpom.xmlNot changed (intentionally):
skipCleanStaleClassesdefault staysfalse(CI safety)useIncrementalCompilation=falsestays (MCOMPILER-209 still open)target/(not committed to git)Net result: 3 files changed, +22/−38 lines (net −16)
How to test
cd server/application-server./mvnw clean compile— full build (~40s)./mvnw compile— should complete in ~3-5s (not recompiling generated files)rm -rf target && ./mvnw compile— full rebuild (quick profile not active)Summary by CodeRabbit