This is an automated email from the ASF dual-hosted git repository.

olamy pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/maven-build-cache-extension.git


The following commit(s) were added to refs/heads/master by this push:
     new e739cfa  Save attached outputs for compile-only cache entries (#394)
e739cfa is described below

commit e739cfafb6837a484a9f3bd3b92023c500ef5bb8
Author: Gili Tzabari <[email protected]>
AuthorDate: Thu Dec 25 04:00:51 2025 -0500

    Save attached outputs for compile-only cache entries (#394)
    
    * Save attached outputs for compile-only cache entries
---
 .../BuildCacheMojosExecutionStrategy.java          |  62 ++-
 .../apache/maven/buildcache/CacheController.java   |  20 +
 .../maven/buildcache/CacheControllerImpl.java      | 609 +++++++++++++++++++--
 .../apache/maven/buildcache/xml/CacheConfig.java   |  11 +
 .../maven/buildcache/xml/CacheConfigImpl.java      |   6 +
 src/main/mdo/build-cache-build.mdo                 |   5 +
 src/site/markdown/how-to.md                        |  15 +
 src/site/markdown/parameters.md                    |   1 +
 .../maven/buildcache/its/BuildExtensionTest.java   |   1 +
 .../buildcache/its/CacheCompileDisabledTest.java   | 139 +++++
 .../buildcache/its/Issue393CompileRestoreTest.java |  53 ++
 .../maven/buildcache/its/MandatoryCleanTest.java   |   4 +
 .../maven/buildcache/its/StaleArtifactTest.java    |  83 +++
 .../its/StaleMultimoduleArtifactTest.java          | 105 ++++
 .../issue-393-compile-restore/.mvn/extensions.xml  |  28 +
 .../.mvn/maven-build-cache-config.xml              |  34 ++
 .../projects/issue-393-compile-restore/app/pom.xml |  37 ++
 .../app/src/main/java/module-info.java             |  21 +
 .../maven/caching/test/jpms/app/Greeting.java      |  30 +
 .../issue-393-compile-restore/consumer/pom.xml     |  64 +++
 .../consumer/src/main/java/module-info.java        |  22 +
 .../maven/caching/test/jpms/consumer/Consumer.java |  32 ++
 .../caching/test/jpms/consumer/ConsumerTest.java   |  31 ++
 .../projects/issue-393-compile-restore/pom.xml     |  42 ++
 .../.mvn/maven-build-cache-config.xml              |  32 ++
 src/test/projects/stale-artifact/pom.xml           |  46 ++
 .../src/main/java/org/example/App.java             |  25 +
 .../.mvn/maven-build-cache-config.xml              |  32 ++
 .../stale-multimodule-artifact/module1/pom.xml     |  31 ++
 .../module1/src/main/java/org/example/Module1.java |  25 +
 .../projects/stale-multimodule-artifact/pom.xml    |  47 ++
 31 files changed, 1616 insertions(+), 77 deletions(-)

diff --git 
a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
 
b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
index 863d3e4..d4715ed 100644
--- 
a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
+++ 
b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
@@ -23,6 +23,7 @@
 import javax.inject.Named;
 
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.HashSet;
 import java.util.List;
@@ -142,33 +143,58 @@ public void execute(
 
             boolean restorable = result.isSuccess() || 
result.isPartialSuccess();
             boolean restored = false; // if partially restored need to save 
increment
+
             if (restorable) {
                 CacheRestorationStatus cacheRestorationStatus =
                         restoreProject(result, mojoExecutions, 
mojoExecutionRunner, cacheConfig);
                 restored = CacheRestorationStatus.SUCCESS == 
cacheRestorationStatus;
                 executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, 
cleanPhase, mojoExecutionRunner);
             }
-            if (!restored) {
-                for (MojoExecution mojoExecution : mojoExecutions) {
-                    if (source == Source.CLI
-                            || mojoExecution.getLifecyclePhase() == null
-                            || 
lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) 
{
-                        mojoExecutionRunner.run(mojoExecution);
+
+            try {
+                if (!restored && !forkedExecution) {
+                    // Move pre-existing artifacts to staging directory to 
prevent caching stale files
+                    // from previous builds (e.g., after source changes or 
from cache restored
+                    // with clock skew). This ensures save() only sees fresh 
files built during this session.
+                    // Skip for forked executions since they don't cache and 
shouldn't modify artifacts.
+                    try {
+                        cacheController.stagePreExistingArtifacts(session, 
project);
+                    } catch (IOException e) {
+                        LOGGER.debug("Failed to stage pre-existing artifacts: 
{}", e.getMessage());
+                        // Continue build - if staging fails, we'll just cache 
what exists
                     }
                 }
-            }
 
-            if (cacheState == INITIALIZED && (!result.isSuccess() || 
!restored)) {
-                if (cacheConfig.isSkipSave()) {
-                    LOGGER.info("Cache saving is disabled.");
-                } else if (cacheConfig.isMandatoryClean()
-                        && lifecyclePhasesHelper
-                                .getCleanSegment(project, mojoExecutions)
-                                .isEmpty()) {
-                    LOGGER.info("Cache storing is skipped since there was no 
\"clean\" phase.");
-                } else {
-                    final Map<String, MojoExecutionEvent> executionEvents = 
mojoListener.getProjectExecutions(project);
-                    cacheController.save(result, mojoExecutions, 
executionEvents);
+                if (!restored) {
+                    for (MojoExecution mojoExecution : mojoExecutions) {
+                        if (source == Source.CLI
+                                || mojoExecution.getLifecyclePhase() == null
+                                || 
lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) 
{
+                            mojoExecutionRunner.run(mojoExecution);
+                        }
+                    }
+                }
+
+                if (cacheState == INITIALIZED && (!result.isSuccess() || 
!restored)) {
+                    if (cacheConfig.isSkipSave()) {
+                        LOGGER.debug("Cache saving is disabled.");
+                    } else if (cacheConfig.isMandatoryClean()
+                            && lifecyclePhasesHelper
+                                    .getCleanSegment(project, mojoExecutions)
+                                    .isEmpty()) {
+                        LOGGER.debug("Cache storing is skipped since there was 
no \"clean\" phase.");
+                    } else {
+                        final Map<String, MojoExecutionEvent> executionEvents =
+                                mojoListener.getProjectExecutions(project);
+                        cacheController.save(result, mojoExecutions, 
executionEvents);
+                    }
+                }
+            } finally {
+                // Always restore staged files after build completes (whether 
save ran or not).
+                // Files that were rebuilt are discarded; files that weren't 
rebuilt are restored.
+                // Skip for forked executions since they don't stage artifacts.
+                if (!restored && !forkedExecution) {
+                    cacheController.restoreStagedArtifacts(session, project);
                 }
             }
 
diff --git a/src/main/java/org/apache/maven/buildcache/CacheController.java 
b/src/main/java/org/apache/maven/buildcache/CacheController.java
index 7acf785..7d8f578 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheController.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheController.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.buildcache;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -45,4 +46,23 @@ void save(
     boolean isForcedExecution(MavenProject project, MojoExecution execution);
 
     void saveCacheReport(MavenSession session);
+
+    /**
+     * Move pre-existing artifacts to staging directory to prevent caching 
stale files.
+     * Called before mojos run to ensure save() only sees fresh files.
+     *
+     * @param session the Maven session
+     * @param project the Maven project
+     * @throws IOException if file operations fail
+     */
+    void stagePreExistingArtifacts(MavenSession session, MavenProject project) 
throws IOException;
+
+    /**
+     * Restore staged artifacts after save() completes.
+     * Files that were rebuilt are discarded; files that weren't rebuilt are 
restored.
+     *
+     * @param session the Maven session
+     * @param project the Maven project
+     */
+    void restoreStagedArtifacts(MavenSession session, MavenProject project);
 }
diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java 
b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
index 48cb5a0..4c57328 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
@@ -30,6 +30,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -40,6 +41,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -134,13 +136,30 @@ public class CacheControllerImpl implements 
CacheController {
     private volatile Scm scm;
 
     /**
-     * A map dedicated to store the base path of resources stored to the cache 
which are not original artifacts
-     * (ex : generated source basedir).
-     * Used to link the resource to its path on disk
+     * Per-project cache state to ensure thread safety in multi-threaded 
builds.
+     * Each project gets isolated state for resource tracking, counters, and 
restored output tracking.
      */
-    private final Map<String, Path> attachedResourcesPathsById = new 
HashMap<>();
+    private static class ProjectCacheState {
+        final Map<String, Path> attachedResourcesPathsById = new HashMap<>();
+        int attachedResourceCounter = 0;
+        final Set<String> restoredOutputClassifiers = new HashSet<>();
+
+        /**
+         * Tracks the staging directory path where pre-existing artifacts are 
moved.
+         * Artifacts are moved here before mojos run and restored after save() 
completes.
+         */
+        Path stagingDirectory;
+    }
+
+    private final ConcurrentMap<String, ProjectCacheState> projectStates = new 
ConcurrentHashMap<>();
 
-    private int attachedResourceCounter = 0;
+    /**
+     * Get or create cache state for the given project (thread-safe).
+     */
+    private ProjectCacheState getProjectState(MavenProject project) {
+        String key = getVersionlessProjectKey(project);
+        return projectStates.computeIfAbsent(key, k -> new 
ProjectCacheState());
+    }
     // CHECKSTYLE_OFF: ParameterNumber
     @Inject
     public CacheControllerImpl(
@@ -261,6 +280,7 @@ private CacheResult analyzeResult(CacheContext context, 
List<MojoExecution> mojo
             List<MojoExecution> cachedSegment =
                     
lifecyclePhasesHelper.getCachedSegment(context.getProject(), mojoExecutions, 
build);
             List<MojoExecution> missingMojos = 
build.getMissingExecutions(cachedSegment);
+
             if (!missingMojos.isEmpty()) {
                 LOGGER.warn(
                         "Cached build doesn't contains all requested plugin 
executions "
@@ -312,7 +332,6 @@ private boolean canIgnoreMissingSegment(MavenProject 
project, Build info, List<M
     private UnaryOperator<File> createRestorationToDiskConsumer(final 
MavenProject project, final Artifact artifact) {
 
         if (cacheConfig.isRestoreOnDiskArtifacts() && 
MavenProjectInput.isRestoreOnDiskArtifacts(project)) {
-
             Path restorationPath = 
project.getBasedir().toPath().resolve(artifact.getFilePath());
             final AtomicBoolean restored = new AtomicBoolean(false);
             return file -> {
@@ -320,13 +339,11 @@ private UnaryOperator<File> 
createRestorationToDiskConsumer(final MavenProject p
                 if (restored.compareAndSet(false, true)) {
                     verifyRestorationInsideProject(project, restorationPath);
                     try {
-                        Files.createDirectories(restorationPath.getParent());
-                        Files.copy(file.toPath(), restorationPath, 
StandardCopyOption.REPLACE_EXISTING);
+                        restoreArtifactToDisk(file, artifact, restorationPath);
                     } catch (IOException e) {
                         LOGGER.error("Cannot restore file " + 
artifact.getFileName(), e);
                         throw new RuntimeException(e);
                     }
-                    LOGGER.debug("Restored file on disk ({} to {})", 
artifact.getFileName(), restorationPath);
                 }
                 return restorationPath.toFile();
             };
@@ -335,6 +352,41 @@ private UnaryOperator<File> 
createRestorationToDiskConsumer(final MavenProject p
         return file -> file;
     }
 
+    /**
+     * Restores an artifact from cache to disk, handling both regular files 
and directory artifacts.
+     * Directory artifacts (cached as zips) are unzipped back to their 
original directory structure.
+     */
+    private void restoreArtifactToDisk(File cachedFile, Artifact artifact, 
Path restorationPath) throws IOException {
+        // Check the explicit isDirectory flag set during save.
+        // Directory artifacts (e.g., target/classes) are saved as zips and 
need to be unzipped on restore.
+        if (artifact.isIsDirectory()) {
+            restoreDirectoryArtifact(cachedFile, artifact, restorationPath);
+        } else {
+            restoreRegularFileArtifact(cachedFile, artifact, restorationPath);
+        }
+    }
+
+    /**
+     * Restores a directory artifact by unzipping the cached zip file.
+     */
+    private void restoreDirectoryArtifact(File cachedZip, Artifact artifact, 
Path restorationPath) throws IOException {
+        if (!Files.exists(restorationPath)) {
+            Files.createDirectories(restorationPath);
+        }
+        CacheUtils.unzip(cachedZip.toPath(), restorationPath, 
cacheConfig.isPreservePermissions());
+        LOGGER.debug("Restored directory artifact by unzipping: {} -> {}", 
artifact.getFileName(), restorationPath);
+    }
+
+    /**
+     * Restores a regular file artifact by copying it from cache.
+     */
+    private void restoreRegularFileArtifact(File cachedFile, Artifact 
artifact, Path restorationPath)
+            throws IOException {
+        Files.createDirectories(restorationPath.getParent());
+        Files.copy(cachedFile.toPath(), restorationPath, 
StandardCopyOption.REPLACE_EXISTING);
+        LOGGER.debug("Restored file on disk ({} to {})", 
artifact.getFileName(), restorationPath);
+    }
+
     private boolean isPathInsideProject(final MavenProject project, Path path) 
{
         Path restorationPath = path.toAbsolutePath().normalize();
         return restorationPath.startsWith(project.getBasedir().toPath());
@@ -355,6 +407,7 @@ public ArtifactRestorationReport 
restoreProjectArtifacts(CacheResult cacheResult
         final Build build = cacheResult.getBuildInfo();
         final CacheContext context = cacheResult.getContext();
         final MavenProject project = context.getProject();
+        final ProjectCacheState state = getProjectState(project);
         ArtifactRestorationReport restorationReport = new 
ArtifactRestorationReport();
 
         try {
@@ -396,6 +449,8 @@ public ArtifactRestorationReport 
restoreProjectArtifacts(CacheResult cacheResult
                             final Path attachedArtifactFile =
                                     localCache.getArtifactFile(context, 
cacheResult.getSource(), attachedArtifactInfo);
                             restoreGeneratedSources(attachedArtifactInfo, 
attachedArtifactFile, project);
+                            // Track this classifier as restored so save() 
includes it even with old timestamp
+                            
state.restoredOutputClassifiers.add(attachedArtifactInfo.getClassifier());
                         }
                     } else {
                         Future<File> downloadTask = createDownloadTask(
@@ -496,29 +551,71 @@ public void save(
 
         final MavenProject project = context.getProject();
         final MavenSession session = context.getSession();
+        final ProjectCacheState state = getProjectState(project);
         try {
+            state.attachedResourcesPathsById.clear();
+            state.attachedResourceCounter = 0;
+
+            // Get build start time to filter out stale artifacts from 
previous builds
+            final long buildStartTime = 
session.getRequest().getStartTime().getTime();
+
             final HashFactory hashFactory = cacheConfig.getHashFactory();
+            final HashAlgorithm algorithm = hashFactory.createAlgorithm();
             final org.apache.maven.artifact.Artifact projectArtifact = 
project.getArtifact();
-            final List<org.apache.maven.artifact.Artifact> attachedArtifacts;
-            final List<Artifact> attachedArtifactDtos;
-            final Artifact projectArtifactDto;
-            if (project.hasLifecyclePhase("package")) {
-                final HashAlgorithm algorithm = hashFactory.createAlgorithm();
-                attachGeneratedSources(project);
-                attachOutputs(project);
-                attachedArtifacts = project.getAttachedArtifacts() != null
-                        ? project.getAttachedArtifacts()
-                        : Collections.emptyList();
-                attachedArtifactDtos = artifactDtos(attachedArtifacts, 
algorithm, project);
-                projectArtifactDto = artifactDto(project.getArtifact(), 
algorithm, project);
-            } else {
-                attachedArtifacts = Collections.emptyList();
-                attachedArtifactDtos = new ArrayList<>();
-                projectArtifactDto = null;
+
+            // Cache compile outputs (classes, test-classes, generated 
sources) if enabled
+            // This allows compile-only builds to create restorable cache 
entries
+            // Can be disabled with -Dmaven.build.cache.cacheCompile=false to 
reduce IO overhead
+            final boolean cacheCompile = cacheConfig.isCacheCompile();
+            if (cacheCompile) {
+                attachGeneratedSources(project, state, buildStartTime);
+                attachOutputs(project, state, buildStartTime);
             }
 
+            final List<org.apache.maven.artifact.Artifact> attachedArtifacts =
+                    project.getAttachedArtifacts() != null ? 
project.getAttachedArtifacts() : Collections.emptyList();
+            final List<Artifact> attachedArtifactDtos = 
artifactDtos(attachedArtifacts, algorithm, project, state);
+            // Always create artifact DTO - if package phase hasn't run, the 
file will be null
+            // and restoration will safely skip it. This ensures all builds 
have an artifact DTO.
+            final Artifact projectArtifactDto = 
artifactDto(project.getArtifact(), algorithm, project, state);
+
             List<CompletedExecution> completedExecution = 
buildExecutionInfo(mojoExecutions, executionEvents);
 
+            // CRITICAL: Don't create incomplete cache entries!
+            // Only save cache entry if we have SOMETHING useful to restore.
+            // Exclude consumer POMs (Maven metadata) from the "useful 
artifacts" check.
+            // This prevents the bug where:
+            //   1. mvn compile (cacheCompile=false) creates cache entry with 
only metadata
+            //   2. mvn compile (cacheCompile=true) tries to restore 
incomplete cache and fails
+            //
+            // Save cache entry if ANY of these conditions are met:
+            // 1. Project artifact file exists:
+            //    a) Regular file (JAR/WAR/etc from package phase)
+            //    b) Directory (target/classes from compile-only builds) - 
only if cacheCompile=true
+            // 2. Has attached artifacts (classes/test-classes from 
cacheCompile=true)
+            // 3. POM project with plugin executions (worth caching to skip 
plugin execution on cache hit)
+            //
+            // NOTE: No timestamp checking needed - 
stagePreExistingArtifacts() ensures only fresh files
+            // are visible (stale files are moved to staging directory).
+
+            // Check if project artifact is valid (exists and is correct type)
+            boolean hasArtifactFile = projectArtifact.getFile() != null
+                    && projectArtifact.getFile().exists()
+                    && (projectArtifact.getFile().isFile()
+                            || (cacheCompile && 
projectArtifact.getFile().isDirectory()));
+            boolean hasAttachedArtifacts = !attachedArtifactDtos.isEmpty()
+                    && attachedArtifactDtos.stream()
+                            .anyMatch(a -> 
!"consumer".equals(a.getClassifier()) || !"pom".equals(a.getType()));
+            // Only save POM projects if they executed plugins (not just 
aggregator POMs with no work)
+            boolean isPomProjectWithWork = 
"pom".equals(project.getPackaging()) && !completedExecution.isEmpty();
+
+            if (!hasArtifactFile && !hasAttachedArtifacts && 
!isPomProjectWithWork) {
+                LOGGER.info(
+                        "Skipping cache save: no artifacts to save ({}only 
metadata present)",
+                        cacheCompile ? "" : "cacheCompile=false, ");
+                return;
+            }
+
             final Build build = new Build(
                     session.getGoals(),
                     projectArtifactDto,
@@ -532,23 +629,21 @@ public void save(
 
             localCache.beforeSave(context);
 
-            // if package phase presence means new artifacts were packaged
-            if (project.hasLifecyclePhase("package")) {
-                if (projectArtifact.getFile() != null) {
-                    localCache.saveArtifactFile(cacheResult, projectArtifact);
-                }
-                for (org.apache.maven.artifact.Artifact attachedArtifact : 
attachedArtifacts) {
-                    if (attachedArtifact.getFile() != null) {
-                        boolean storeArtifact =
-                                
isOutputArtifact(attachedArtifact.getFile().getName());
-                        if (storeArtifact) {
-                            localCache.saveArtifactFile(cacheResult, 
attachedArtifact);
-                        } else {
-                            LOGGER.debug(
-                                    "Skipping attached project artifact '{}' = 
"
-                                            + " it is marked for exclusion 
from caching",
-                                    attachedArtifact.getFile().getName());
-                        }
+            // Save project artifact file if it exists (created by package or 
compile phase)
+            if (projectArtifact.getFile() != null) {
+                saveProjectArtifact(cacheResult, projectArtifact, project);
+            }
+            for (org.apache.maven.artifact.Artifact attachedArtifact : 
attachedArtifacts) {
+                if (attachedArtifact.getFile() != null) {
+                    boolean storeArtifact =
+                            
isOutputArtifact(attachedArtifact.getFile().getName());
+                    if (storeArtifact) {
+                        localCache.saveArtifactFile(cacheResult, 
attachedArtifact);
+                    } else {
+                        LOGGER.debug(
+                                "Skipping attached project artifact '{}' = "
+                                        + " it is marked for exclusion from 
caching",
+                                attachedArtifact.getFile().getName());
                     }
                 }
             }
@@ -566,6 +661,58 @@ public void save(
             } catch (Exception ex) {
                 LOGGER.error("Failed to clean cache due to unexpected error:", 
ex);
             }
+        } finally {
+            // Cleanup project state to free memory, but preserve 
stagingDirectory for restore
+            // Note: stagingDirectory must persist until 
restoreStagedArtifacts() is called
+            state.attachedResourcesPathsById.clear();
+            state.attachedResourceCounter = 0;
+            state.restoredOutputClassifiers.clear();
+            // stagingDirectory is NOT cleared here - it's cleared in 
restoreStagedArtifacts()
+        }
+    }
+
+    /**
+     * Saves a project artifact to cache, handling both regular files and 
directory artifacts.
+     * Directory artifacts (e.g., target/classes from compile-only builds) are 
zipped before saving
+     * since Files.copy() cannot handle directories.
+     */
+    private void saveProjectArtifact(
+            CacheResult cacheResult, org.apache.maven.artifact.Artifact 
projectArtifact, MavenProject project)
+            throws IOException {
+        File originalFile = projectArtifact.getFile();
+        try {
+            if (originalFile.isDirectory()) {
+                saveDirectoryArtifact(cacheResult, projectArtifact, project, 
originalFile);
+            } else {
+                // Regular file (JAR/WAR) - save directly
+                localCache.saveArtifactFile(cacheResult, projectArtifact);
+            }
+        } finally {
+            // Restore original file reference in case it was temporarily 
changed
+            projectArtifact.setFile(originalFile);
+        }
+    }
+
+    /**
+     * Saves a directory artifact by zipping it first, then saving the zip to 
cache.
+     */
+    private void saveDirectoryArtifact(
+            CacheResult cacheResult,
+            org.apache.maven.artifact.Artifact projectArtifact,
+            MavenProject project,
+            File originalFile)
+            throws IOException {
+        Path tempZip = Files.createTempFile("maven-cache-", "-" + 
project.getArtifactId() + ".zip");
+        boolean hasFiles = CacheUtils.zip(originalFile.toPath(), tempZip, "*", 
cacheConfig.isPreservePermissions());
+        if (hasFiles) {
+            // Temporarily replace artifact file with zip for saving
+            projectArtifact.setFile(tempZip.toFile());
+            localCache.saveArtifactFile(cacheResult, projectArtifact);
+            LOGGER.debug("Saved directory artifact as zip: {} -> {}", 
originalFile, tempZip);
+            // Clean up temp file after it's been saved to cache
+            Files.deleteIfExists(tempZip);
+        } else {
+            LOGGER.info("Skipping empty directory artifact: {}", originalFile);
         }
     }
 
@@ -623,29 +770,43 @@ public void produceDiffReport(CacheResult cacheResult, 
Build build) {
     }
 
     private List<Artifact> artifactDtos(
-            List<org.apache.maven.artifact.Artifact> attachedArtifacts, 
HashAlgorithm digest, MavenProject project)
+            List<org.apache.maven.artifact.Artifact> attachedArtifacts,
+            HashAlgorithm digest,
+            MavenProject project,
+            ProjectCacheState state)
             throws IOException {
         List<Artifact> result = new ArrayList<>();
         for (org.apache.maven.artifact.Artifact attachedArtifact : 
attachedArtifacts) {
             if (attachedArtifact.getFile() != null
                     && isOutputArtifact(attachedArtifact.getFile().getName())) 
{
-                result.add(artifactDto(attachedArtifact, digest, project));
+                result.add(artifactDto(attachedArtifact, digest, project, 
state));
             }
         }
         return result;
     }
 
     private Artifact artifactDto(
-            org.apache.maven.artifact.Artifact projectArtifact, HashAlgorithm 
algorithm, MavenProject project)
+            org.apache.maven.artifact.Artifact projectArtifact,
+            HashAlgorithm algorithm,
+            MavenProject project,
+            ProjectCacheState state)
             throws IOException {
         final Artifact dto = DtoUtils.createDto(projectArtifact);
-        if (projectArtifact.getFile() != null && 
projectArtifact.getFile().isFile()) {
+        if (projectArtifact.getFile() != null) {
             final Path file = projectArtifact.getFile().toPath();
-            dto.setFileHash(algorithm.hash(file));
-            dto.setFileSize(Files.size(file));
 
+            // Only set hash and size for regular files (not directories like 
target/classes for JPMS projects)
+            if (Files.isRegularFile(file)) {
+                dto.setFileHash(algorithm.hash(file));
+                dto.setFileSize(Files.size(file));
+            } else if (Files.isDirectory(file)) {
+                // Mark directory artifacts explicitly so we can unzip them on 
restore
+                dto.setIsDirectory(true);
+            }
+
+            // Always set filePath (needed for artifact restoration)
             // Get the relative path of any extra zip directory added to the 
cache
-            Path relativePath = 
attachedResourcesPathsById.get(projectArtifact.getClassifier());
+            Path relativePath = 
state.attachedResourcesPathsById.get(projectArtifact.getClassifier());
             if (relativePath == null) {
                 // If the path was not a member of this map, we are in 
presence of an original artifact.
                 // we get its location on the disk
@@ -899,15 +1060,29 @@ private void restoreGeneratedSources(Artifact artifact, 
Path artifactFilePath, M
     }
 
     // TODO: move to config
-    public void attachGeneratedSources(MavenProject project) throws 
IOException {
+    public void attachGeneratedSources(MavenProject project, ProjectCacheState 
state, long buildStartTime)
+            throws IOException {
         final Path targetDir = Paths.get(project.getBuild().getDirectory());
 
         final Path generatedSourcesDir = 
targetDir.resolve("generated-sources");
-        attachDirIfNotEmpty(generatedSourcesDir, targetDir, project, 
OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
+        attachDirIfNotEmpty(
+                generatedSourcesDir,
+                targetDir,
+                project,
+                state,
+                OutputType.GENERATED_SOURCE,
+                DEFAULT_FILE_GLOB,
+                buildStartTime);
 
         final Path generatedTestSourcesDir = 
targetDir.resolve("generated-test-sources");
         attachDirIfNotEmpty(
-                generatedTestSourcesDir, targetDir, project, 
OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
+                generatedTestSourcesDir,
+                targetDir,
+                project,
+                state,
+                OutputType.GENERATED_SOURCE,
+                DEFAULT_FILE_GLOB,
+                buildStartTime);
 
         Set<String> sourceRoots = new TreeSet<>();
         if (project.getCompileSourceRoots() != null) {
@@ -923,18 +1098,26 @@ public void attachGeneratedSources(MavenProject project) 
throws IOException {
                     && sourceRootPath.startsWith(targetDir)
                     && !(sourceRootPath.startsWith(generatedSourcesDir)
                             || 
sourceRootPath.startsWith(generatedTestSourcesDir))) { // dir within target
-                attachDirIfNotEmpty(sourceRootPath, targetDir, project, 
OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB);
+                attachDirIfNotEmpty(
+                        sourceRootPath,
+                        targetDir,
+                        project,
+                        state,
+                        OutputType.GENERATED_SOURCE,
+                        DEFAULT_FILE_GLOB,
+                        buildStartTime);
             }
         }
     }
 
-    private void attachOutputs(MavenProject project) throws IOException {
+    private void attachOutputs(MavenProject project, ProjectCacheState state, 
long buildStartTime) throws IOException {
         final List<DirName> attachedDirs = cacheConfig.getAttachedOutputs();
         for (DirName dir : attachedDirs) {
             final Path targetDir = 
Paths.get(project.getBuild().getDirectory());
             final Path outputDir = targetDir.resolve(dir.getValue());
             if (isPathInsideProject(project, outputDir)) {
-                attachDirIfNotEmpty(outputDir, targetDir, project, 
OutputType.EXTRA_OUTPUT, dir.getGlob());
+                attachDirIfNotEmpty(
+                        outputDir, targetDir, project, state, 
OutputType.EXTRA_OUTPUT, dir.getGlob(), buildStartTime);
             } else {
                 LOGGER.warn("Outside project output candidate directory 
discarded ({})", outputDir.normalize());
             }
@@ -945,16 +1128,25 @@ private void attachDirIfNotEmpty(
             Path candidateSubDir,
             Path parentDir,
             MavenProject project,
+            ProjectCacheState state,
             final OutputType attachedOutputType,
-            final String glob)
+            final String glob,
+            final long buildStartTime)
             throws IOException {
         if (Files.isDirectory(candidateSubDir) && hasFiles(candidateSubDir)) {
             final Path relativePath = 
project.getBasedir().toPath().relativize(candidateSubDir);
-            attachedResourceCounter++;
-            final String classifier = attachedOutputType.getClassifierPrefix() 
+ attachedResourceCounter;
+            state.attachedResourceCounter++;
+            final String classifier = attachedOutputType.getClassifierPrefix() 
+ state.attachedResourceCounter;
+
+            // NOTE: No timestamp checking needed - 
stagePreExistingArtifacts() ensures stale files
+            // are moved to staging. If files exist here, they're either:
+            // 1. Fresh files built during this session, or
+            // 2. Files restored from cache during this session
+            // Both cases are valid and should be cached.
+
             boolean success = zipAndAttachArtifact(project, candidateSubDir, 
classifier, glob);
             if (success) {
-                attachedResourcesPathsById.put(classifier, relativePath);
+                state.attachedResourcesPathsById.put(classifier, relativePath);
                 LOGGER.debug("Attached directory: {}", candidateSubDir);
             }
         }
@@ -973,6 +1165,305 @@ public FileVisitResult visitFile(Path path, 
BasicFileAttributes basicFileAttribu
         return hasFiles.booleanValue();
     }
 
+    /**
+     * Move pre-existing build artifacts to staging directory to prevent 
caching stale files.
+     *
+     * <p><b>Artifacts Staged:</b>
+     * <ul>
+     *   <li>{@code target/classes} - Compiled main classes directory</li>
+     *   <li>{@code target/test-classes} - Compiled test classes directory</li>
+     *   <li>{@code target/*.jar} - Main project artifact (JAR/WAR files)</li>
+     *   <li>Other directories configured via {@code attachedOutputs} in cache 
configuration</li>
+     * </ul>
+     *
+     * <p><b>DESIGN RATIONALE - Staleness Detection via Staging Directory:</b>
+     *
+     * <p>This approach solves three critical problems that timestamp-based 
checking cannot handle:
+     *
+     * <p><b>Problem 1: Future Timestamps from Clock Skew</b>
+     * <ul>
+     *   <li>Machine A (clock ahead at 11:00 AM) builds and caches artifacts
+     *   <li>Machine B (correct clock at 10:00 AM) restores cache
+     *   <li>Restored files have timestamps from the future (11:00 AM)
+     *   <li>User switches branches or updates sources (sources timestamped 
10:02 AM)
+     *   <li>Maven incremental compiler sees: sources (10:02 AM) &lt; classes 
(11:00 AM)
+     *   <li>Maven skips compilation (thinks sources older than classes)
+     *   <li>Wrong classes from old source version get cached!
+     * </ul>
+     *
+     * <p><b>Problem 2: Orphaned Class Files from Deleted Sources</b>
+     * <ul>
+     *   <li>Version A has Foo.java → compiles Foo.class
+     *   <li>Switch to Version B (no Foo.java)
+     *   <li>Foo.class remains in target/classes (orphaned)
+     *   <li>Cache miss on new version triggers mojos
+     *   <li>Without protection, orphaned Foo.class gets cached
+     *   <li>Future cache hits restore Foo.class (which shouldn't exist!)
+     * </ul>
+     *
+     * <p><b>Problem 3: Stale JARs/WARs from Previous Builds</b>
+     * <ul>
+     *   <li>Yesterday: built myapp.jar on old version
+     *   <li>Today: switched to new version, sources changed
+     *   <li>mvn package runs (cache miss)
+     *   <li>If JAR wasn't rebuilt, stale JAR could be cached
+     * </ul>
+     *
+     * <p><b>Solution: Staging Directory Physical Separation</b>
+     * <ul>
+     *   <li>Before mojos run: Move pre-existing artifacts to 
target/.maven-build-cache-stash/
+     *   <li>Maven sees clean target/ with no pre-existing artifacts
+     *   <li>Maven compiler MUST compile (can't skip based on timestamps)
+     *   <li>Fresh correct files created in target/
+     *   <li>save() only sees fresh files (stale ones are in staging directory)
+     *   <li>After save(): Restore artifacts from staging (delete if fresh 
version exists)
+     * </ul>
+     *
+     * <p><b>Why Better Than Timestamp Checking:</b>
+     * <ul>
+     *   <li>No clock skew calculations needed
+     *   <li>Physical file separation (not heuristics)
+     *   <li>Forces correct incremental compilation
+     *   <li>Handles interrupted builds gracefully (just delete staging 
directory)
+     *   <li>Simpler and more robust
+     *   <li>Easier cleanup - delete one directory instead of filtering files
+     * </ul>
+     *
+     * <p><b>Interrupted Build Handling:</b>
+     * If staging directory exists from interrupted previous run, it's deleted 
and recreated.
+     *
+     * @param session The Maven session
+     * @param project The Maven project being built
+     * @throws IOException if file move operations fail
+     */
+    public void stagePreExistingArtifacts(MavenSession session, MavenProject 
project) throws IOException {
+        final ProjectCacheState state = getProjectState(project);
+        final Path multimoduleRoot = CacheUtils.getMultimoduleRoot(session);
+        final Path stagingDir = 
multimoduleRoot.resolve("target").resolve("maven-build-cache-extension");
+
+        // Create or reuse staging directory from interrupted previous run
+        Files.createDirectories(stagingDir);
+        state.stagingDirectory = stagingDir;
+
+        // Collect all paths that will be cached
+        Set<Path> pathsToProcess = collectCachedArtifactPaths(project);
+
+        int movedCount = 0;
+        for (Path path : pathsToProcess) {
+            // Calculate path relative to multimodule root (preserves full 
path including submodule)
+            Path relativePath = multimoduleRoot.relativize(path);
+            Path stagedPath = stagingDir.resolve(relativePath);
+
+            if (Files.isDirectory(path)) {
+                // If directory already exists in staging (from interrupted 
run), remove it first
+                if (Files.exists(stagedPath)) {
+                    deleteDirectory(stagedPath);
+                    LOGGER.debug("Removed existing staged directory: {}", 
stagedPath);
+                }
+                // Move entire directory to staging
+                Files.createDirectories(stagedPath.getParent());
+                Files.move(path, stagedPath);
+                movedCount++;
+                LOGGER.debug("Moved directory to staging: {} → {}", 
relativePath, stagedPath);
+            } else if (Files.isRegularFile(path)) {
+                // If file already exists in staging (from interrupted run), 
remove it first
+                if (Files.exists(stagedPath)) {
+                    Files.delete(stagedPath);
+                    LOGGER.debug("Removed existing staged file: {}", 
stagedPath);
+                }
+                // Move individual file (e.g., JAR) to staging
+                Files.createDirectories(stagedPath.getParent());
+                Files.move(path, stagedPath);
+                movedCount++;
+                LOGGER.debug("Moved file to staging: {} → {}", relativePath, 
stagedPath);
+            }
+        }
+
+        if (movedCount > 0) {
+            LOGGER.info(
+                    "Moved {} pre-existing artifacts to staging directory to 
prevent caching stale files", movedCount);
+        }
+    }
+
+    /**
+     * Collects paths to all artifacts that will be considered for caching for 
the given project.
+     *
+     * <p>This includes:
+     * <ul>
+     *     <li>the main project artifact file (for example, the built JAR), if 
it has been produced, and</li>
+     *     <li>any attached output directories configured via {@code 
cacheConfig.getAttachedOutputs()} under the
+     *         project's target directory, when {@code 
cacheConfig.isCacheCompile()} is enabled.</li>
+     * </ul>
+     * Only paths that currently exist on disk are included in the returned 
set; non-existent files or directories
+     * are ignored.
+     *
+     * @param project the Maven project whose artifact and attached output 
paths should be collected
+     * @return a set of existing filesystem paths for the project's main 
artifact and configured attached outputs
+     */
+    private Set<Path> collectCachedArtifactPaths(MavenProject project) {
+        Set<Path> paths = new HashSet<>();
+        final org.apache.maven.artifact.Artifact projectArtifact = 
project.getArtifact();
+        final Path targetDir = Paths.get(project.getBuild().getDirectory());
+
+        // 1. Main project artifact (JAR file or target/classes directory)
+        if (projectArtifact.getFile() != null && 
projectArtifact.getFile().exists()) {
+            paths.add(projectArtifact.getFile().toPath());
+        }
+
+        // 2. Attached outputs from configuration (if cacheCompile enabled)
+        if (cacheConfig.isCacheCompile()) {
+            List<DirName> attachedDirs = cacheConfig.getAttachedOutputs();
+            for (DirName dir : attachedDirs) {
+                Path outputDir = targetDir.resolve(dir.getValue());
+                if (Files.exists(outputDir)) {
+                    paths.add(outputDir);
+                }
+            }
+        }
+
+        return paths;
+    }
+
+    /**
+     * Restore artifacts from staging directory after save() completes.
+     *
+     * <p>For each artifact in staging:
+     * <ul>
+     *   <li>If fresh version exists in target/: Delete staged version (was 
rebuilt correctly)
+     *   <li>If fresh version missing: Move staged version back to target/ 
(wasn't rebuilt, still valid)
+     * </ul>
+     *
+     * <p>This ensures:
+     * <ul>
+     *   <li>save() only cached fresh files (stale ones were in staging 
directory)
+     *   <li>Developers see complete target/ directory after build
+     *   <li>Incremental builds work correctly (unchanged files restored)
+     * </ul>
+     *
+     * <p>Finally, deletes the staging directory.
+     *
+     * @param session The Maven session
+     * @param project The Maven project being built
+     */
+    public void restoreStagedArtifacts(MavenSession session, MavenProject 
project) {
+        final ProjectCacheState state = getProjectState(project);
+        final Path stagingDir = state.stagingDirectory;
+
+        if (stagingDir == null || !Files.exists(stagingDir)) {
+            return; // Nothing to restore
+        }
+
+        try {
+            final Path multimoduleRoot = 
CacheUtils.getMultimoduleRoot(session);
+
+            // Collect directories to delete (where fresh versions exist)
+            final List<Path> dirsToDelete = new ArrayList<>();
+
+            // Walk staging directory and process files
+            Files.walkFileTree(stagingDir, new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult preVisitDirectory(Path dir, 
BasicFileAttributes attrs) throws IOException {
+                    if (dir.equals(stagingDir)) {
+                        return FileVisitResult.CONTINUE; // Skip root
+                    }
+
+                    Path relativePath = stagingDir.relativize(dir);
+                    Path targetPath = multimoduleRoot.resolve(relativePath);
+
+                    if (Files.exists(targetPath)) {
+                        // Fresh directory exists - mark entire tree for 
deletion
+                        dirsToDelete.add(dir);
+                        LOGGER.debug("Fresh directory exists, marking for 
recursive deletion: {}", relativePath);
+                        return FileVisitResult.SKIP_SUBTREE;
+                    }
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult visitFile(Path file, 
BasicFileAttributes attrs) throws IOException {
+                    Path relativePath = stagingDir.relativize(file);
+                    Path targetPath = multimoduleRoot.resolve(relativePath);
+
+                    try {
+                        // Atomically move file back if destination doesn't 
exist
+                        Files.createDirectories(targetPath.getParent());
+                        Files.move(file, targetPath);
+                        LOGGER.debug("Restored unchanged file from staging: 
{}", relativePath);
+                    } catch (FileAlreadyExistsException e) {
+                        // Fresh file exists (was rebuilt) - delete stale 
version
+                        Files.delete(file);
+                        LOGGER.debug("Fresh file exists, deleted stale file: 
{}", relativePath);
+                    }
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult postVisitDirectory(Path dir, 
IOException exc) throws IOException {
+                    if (exc != null) {
+                        throw exc;
+                    }
+                    // Try to delete empty directories bottom-up
+                    if (!dir.equals(stagingDir)) {
+                        try {
+                            Files.delete(dir);
+                            LOGGER.debug("Deleted empty directory: {}", 
stagingDir.relativize(dir));
+                        } catch (IOException e) {
+                            // Not empty yet - other modules may still have 
files here
+                        }
+                    }
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+
+            // Recursively delete directories where fresh versions exist
+            for (Path dirToDelete : dirsToDelete) {
+                LOGGER.debug("Recursively deleting stale directory: {}", 
stagingDir.relativize(dirToDelete));
+                deleteDirectory(dirToDelete);
+            }
+
+            // Try to delete staging directory itself if now empty
+            try {
+                Files.delete(stagingDir);
+                LOGGER.debug("Deleted empty staging directory: {}", 
stagingDir);
+            } catch (IOException e) {
+                LOGGER.debug("Staging directory not empty, preserving for 
other modules");
+            }
+
+        } catch (IOException e) {
+            LOGGER.warn("Failed to restore artifacts from staging directory: 
{}", e.getMessage());
+        }
+
+        // Clear the staging directory reference
+        state.stagingDirectory = null;
+
+        // Remove the project state from map to free memory (called after 
save() cleanup)
+        String key = getVersionlessProjectKey(project);
+        projectStates.remove(key);
+    }
+
+    /**
+     * Recursively delete a directory and all its contents.
+     */
+    private void deleteDirectory(Path dir) throws IOException {
+        if (!Files.exists(dir)) {
+            return;
+        }
+
+        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException 
exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
     private boolean isOutputArtifact(String name) {
         List<Pattern> excludePatterns = cacheConfig.getExcludePatterns();
         for (Pattern pattern : excludePatterns) {
diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java 
b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
index 27d6d94..452dcfb 100644
--- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
+++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java
@@ -154,4 +154,15 @@ public interface CacheConfig {
      * Flag to save in cache only if a build went through the clean lifecycle
      */
     boolean isMandatoryClean();
+
+    /**
+     * Flag to cache compile phase outputs (classes, test-classes, generated 
sources).
+     * When enabled (default), compile-only builds create cache entries that 
can be restored
+     * by subsequent builds. When disabled, caching only occurs during package 
phase or later.
+     * <p>
+     * Use: -Dmaven.build.cache.cacheCompile=(true|false)
+     * <p>
+     * Default: true
+     */
+    boolean isCacheCompile();
 }
diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java 
b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
index 70ab047..7d7b91e 100644
--- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
+++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java
@@ -97,6 +97,7 @@ public class CacheConfigImpl implements 
org.apache.maven.buildcache.xml.CacheCon
     public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = 
"maven.build.cache.restoreGeneratedSources";
     public static final String ALWAYS_RUN_PLUGINS = 
"maven.build.cache.alwaysRunPlugins";
     public static final String MANDATORY_CLEAN = 
"maven.build.cache.mandatoryClean";
+    public static final String CACHE_COMPILE = 
"maven.build.cache.cacheCompile";
 
     /**
      * Flag to control if we should skip lookup for cached artifacts globally 
or for a particular project even if
@@ -541,6 +542,11 @@ public boolean isMandatoryClean() {
         return getProperty(MANDATORY_CLEAN, 
getConfiguration().isMandatoryClean());
     }
 
+    @Override
+    public boolean isCacheCompile() {
+        return getProperty(CACHE_COMPILE, true);
+    }
+
     @Override
     public String getId() {
         checkInitializedState();
diff --git a/src/main/mdo/build-cache-build.mdo 
b/src/main/mdo/build-cache-build.mdo
index 0e63935..2d80d85 100644
--- a/src/main/mdo/build-cache-build.mdo
+++ b/src/main/mdo/build-cache-build.mdo
@@ -244,6 +244,11 @@ under the License.
           <name>filePath</name>
           <type>String</type>
         </field>
+        <field>
+          <name>isDirectory</name>
+          <type>boolean</type>
+          <description>Indicates if this artifact represents a directory 
(e.g., target/classes) that was zipped for caching</description>
+        </field>
       <!--/xs:sequence-->
       </fields>
     <!--/xs:complexType-->
diff --git a/src/site/markdown/how-to.md b/src/site/markdown/how-to.md
index a4ebbb7..ce7fa6d 100644
--- a/src/site/markdown/how-to.md
+++ b/src/site/markdown/how-to.md
@@ -227,3 +227,18 @@ Set attribute `excludeDependencies` to `true` in 
`input/plugins/plugin` section:
       </plugins>
     </input>
 ```
+
+### I want to disable caching of compile-only builds
+
+By default, the cache extension saves build outputs when running compile-only 
phases (like `mvn compile` or `mvn test-compile`).
+This allows subsequent builds to restore compiled classes without 
recompilation. To disable this behavior and only cache
+builds that reach the package phase or later:
+
+```shell
+mvn compile -Dmaven.build.cache.cacheCompile=false
+```
+
+This is useful when:
+* You want to ensure cache entries always contain packaged artifacts (JARs, 
WARs, etc.)
+* Your workflow relies on artifacts being available in the local repository
+* You prefer the traditional behavior where only complete builds are cached
diff --git a/src/site/markdown/parameters.md b/src/site/markdown/parameters.md
index e248b00..c03886a 100644
--- a/src/site/markdown/parameters.md
+++ b/src/site/markdown/parameters.md
@@ -39,6 +39,7 @@ This document contains various configuration parameters 
supported by the cache e
 | `-Dmaven.build.cache.skipCache=(true/false)`               | Skip looking up 
artifacts in caches. Does not affect writing artifacts to caches, disables only 
reading when set to `true`               | May be used to trigger a forced 
rebuild when matching artifacts do exist in caches |
 | `-Dmaven.build.cache.skipSave=(true/false)`            | Skip writing build 
result in caches. Does not affect reading from the cache.               | 
Configuring MR builds to benefits from the cache, but restricting writes to the 
`master` branch |
 | `-Dmaven.build.cache.mandatoryClean=(true/false)`          | Enable or 
disable the necessity to execute the `clean` phase in order to store the build 
result in cache                                  | Reducing the risk to save 
"wrong" files in cache in a local dev environnement      |
+| `-Dmaven.build.cache.cacheCompile=(true/false)`            | Cache compile 
phase outputs (classes, test-classes, generated sources). When enabled 
(default), compile-only builds create cache entries that can be restored by 
subsequent builds. When disabled, caching only occurs during package phase or 
later. | Performance optimization for incremental builds                        
            |
 
 ### Project-level properties
 
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/BuildExtensionTest.java 
b/src/test/java/org/apache/maven/buildcache/its/BuildExtensionTest.java
index 382ba76..bf324db 100644
--- a/src/test/java/org/apache/maven/buildcache/its/BuildExtensionTest.java
+++ b/src/test/java/org/apache/maven/buildcache/its/BuildExtensionTest.java
@@ -58,6 +58,7 @@ void skipSaving(Verifier verifier) throws 
VerificationException, IOException {
         verifier.getCliOptions().clear();
         verifier.addCliOption("-D" + CACHE_LOCATION_PROPERTY_NAME + "=" + 
tempDirectory.toAbsolutePath());
         verifier.addCliOption("-D" + SKIP_SAVE + "=true");
+        verifier.addCliOption("--debug");
 
         verifier.setLogFileName("../log-1.txt");
         verifier.executeGoal("verify");
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java 
b/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java
new file mode 100644
index 0000000..d4241d4
--- /dev/null
+++ 
b/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.its;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that the maven.build.cache.cacheCompile property correctly disables
+ * caching of compile-phase outputs.
+ */
+@IntegrationTest("src/test/projects/issue-393-compile-restore")
+class CacheCompileDisabledTest {
+
+    @Test
+    void compileDoesNotCacheWhenDisabled(Verifier verifier) throws 
VerificationException, IOException {
+        verifier.setAutoclean(false);
+
+        // The actual cache is stored in target/build-cache (relative to the 
extension root, not test project)
+        Path localCache = 
Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
+                .resolve("target/build-cache");
+
+        // Clean cache before test
+        if (Files.exists(localCache)) {
+            deleteDirectory(localCache);
+        }
+
+        // First compile with cacheCompile disabled - compile only the app 
module to avoid dependency issues
+        verifier.setLogFileName("../log-compile-disabled.txt");
+        verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
+        verifier.addCliOption("-pl");
+        verifier.addCliOption("app");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify NO cache entry was created (no buildinfo.xml in local cache)
+        boolean hasCacheEntry;
+        try (Stream<Path> walk = Files.walk(localCache)) {
+            hasCacheEntry = walk.anyMatch(p -> 
p.getFileName().toString().equals("buildinfo.xml"));
+        }
+        assertFalse(hasCacheEntry, "Cache entry should NOT be created when 
maven.build.cache.cacheCompile=false");
+
+        // Clean project and run compile again
+        verifier.setLogFileName("../log-compile-disabled-2.txt");
+        verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
+        verifier.addCliOption("-pl");
+        verifier.addCliOption("app");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify cache miss (should NOT restore from cache)
+        Path logFile = 
Paths.get(verifier.getBasedir()).getParent().resolve("log-compile-disabled-2.txt");
+        String logContent = new String(Files.readAllBytes(logFile));
+        assertFalse(
+                logContent.contains("Found cached build, restoring"),
+                "Should NOT restore from cache when cacheCompile was 
disabled");
+    }
+
+    @Test
+    void compileCreatesCacheEntryWhenEnabled(Verifier verifier) throws 
VerificationException, IOException {
+        verifier.setAutoclean(false);
+
+        // The actual cache is stored in target/build-cache (relative to the 
extension root, not test project)
+        Path localCache = 
Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
+                .resolve("target/build-cache");
+
+        // Clean cache before test
+        if (Files.exists(localCache)) {
+            deleteDirectory(localCache);
+        }
+
+        // First compile with cacheCompile enabled (default) - compile only 
the app module
+        verifier.setLogFileName("../log-compile-enabled.txt");
+        verifier.addCliOption("-pl");
+        verifier.addCliOption("app");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify cache entry WAS created
+        boolean hasCacheEntry;
+        try (Stream<Path> walk = Files.walk(localCache)) {
+            hasCacheEntry = walk.anyMatch(p -> 
p.getFileName().toString().equals("buildinfo.xml"));
+        }
+        assertTrue(hasCacheEntry, "Cache entry should be created when 
maven.build.cache.cacheCompile=true (default)");
+
+        // Clean project and run compile again
+        verifier.setLogFileName("../log-compile-enabled-2.txt");
+        verifier.addCliOption("-pl");
+        verifier.addCliOption("app");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify cache hit (should restore from cache)
+        verifier.verifyTextInLog("Found cached build, restoring");
+        verifier.verifyTextInLog("Skipping plugin execution (cached): 
compiler:compile");
+    }
+
+    private void deleteDirectory(Path directory) throws IOException {
+        if (Files.exists(directory)) {
+            try (Stream<Path> walk = Files.walk(directory)) {
+                walk.sorted((a, b) -> b.compareTo(a)).forEach(path -> {
+                    try {
+                        Files.delete(path);
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                });
+            }
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java 
b/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java
new file mode 100644
index 0000000..7c9714b
--- /dev/null
+++ 
b/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.its;
+
+import java.util.Arrays;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+@IntegrationTest("src/test/projects/issue-393-compile-restore")
+class Issue393CompileRestoreTest {
+
+    @Test
+    void restoresAttachedOutputsAfterCompileOnlyBuild(Verifier verifier) 
throws VerificationException {
+        verifier.setAutoclean(false);
+
+        verifier.setLogFileName("../log-compile.txt");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+        verifier.verifyFilePresent("app/target/classes/module-info.class");
+        
verifier.verifyFilePresent("consumer/target/classes/module-info.class");
+
+        verifier.setLogFileName("../log-verify.txt");
+        verifier.executeGoals(Arrays.asList("clean", "verify"));
+        verifier.verifyErrorFreeLog();
+        verifier.verifyTextInLog(
+                "Found cached build, restoring 
org.apache.maven.caching.test.jpms:issue-393-app from cache");
+        verifier.verifyTextInLog("Skipping plugin execution (cached): 
compiler:compile");
+
+        verifier.verifyFilePresent("app/target/classes/module-info.class");
+        
verifier.verifyFilePresent("consumer/target/classes/module-info.class");
+        verifier.verifyFilePresent(
+                
"consumer/target/test-classes/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.class");
+    }
+}
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/MandatoryCleanTest.java 
b/src/test/java/org/apache/maven/buildcache/its/MandatoryCleanTest.java
index 5b25af9..d1253d8 100644
--- a/src/test/java/org/apache/maven/buildcache/its/MandatoryCleanTest.java
+++ b/src/test/java/org/apache/maven/buildcache/its/MandatoryCleanTest.java
@@ -52,6 +52,7 @@ void simple(Verifier verifier) throws VerificationException, 
IOException {
         Path tempDirectory = 
Files.createTempDirectory("simple-mandatory-clean");
         verifier.getCliOptions().clear();
         verifier.addCliOption("-D" + CACHE_LOCATION_PROPERTY_NAME + "=" + 
tempDirectory.toAbsolutePath());
+        verifier.addCliOption("--debug");
 
         verifier.setLogFileName("../log-1.txt");
         verifier.executeGoal("verify");
@@ -99,6 +100,7 @@ void simple(Verifier verifier) throws VerificationException, 
IOException {
     void disabledViaProperty(Verifier verifier) throws VerificationException {
 
         verifier.setAutoclean(false);
+        verifier.addCliOption("--debug");
 
         verifier.setLogFileName("../log-1.txt");
         verifier.executeGoal("verify");
@@ -118,6 +120,7 @@ void disabledViaProperty(Verifier verifier) throws 
VerificationException {
 
         verifier.setLogFileName("../log-2.txt");
         verifier.getCliOptions().clear();
+        verifier.addCliOption("--debug");
         // With "true", we do not change the initially expected behaviour
         verifier.addCliOption("-D" + CacheConfigImpl.MANDATORY_CLEAN + 
"=true");
         verifier.executeGoal("verify");
@@ -137,6 +140,7 @@ void disabledViaProperty(Verifier verifier) throws 
VerificationException {
 
         // With "false", we remove the need for the clean phase
         verifier.getCliOptions().clear();
+        verifier.addCliOption("--debug");
         verifier.addCliOption("-D" + CacheConfigImpl.MANDATORY_CLEAN + 
"=false");
         verifier.setLogFileName("../log-3.txt");
         verifier.executeGoal("verify");
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/StaleArtifactTest.java 
b/src/test/java/org/apache/maven/buildcache/its/StaleArtifactTest.java
new file mode 100644
index 0000000..52ba081
--- /dev/null
+++ b/src/test/java/org/apache/maven/buildcache/its/StaleArtifactTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.its;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.Arrays;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that stale artifacts from source changes are not cached.
+ * Simulates the scenario:
+ * 1. Build version A (creates target/classes with old content)
+ * 2. Source changes (e.g., branch switch, external update), but 
target/classes remains
+ * 3. Build without 'mvn clean' - should NOT cache stale target/classes
+ */
+@IntegrationTest("src/test/projects/stale-artifact")
+class StaleArtifactTest {
+
+    @Test
+    void staleDirectoryNotCached(Verifier verifier) throws 
VerificationException, IOException {
+        verifier.setAutoclean(false);
+
+        // Build version A: compile project
+        verifier.setLogFileName("../log-version-a.txt");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        Path classesDir = Paths.get(verifier.getBasedir(), "target", 
"classes");
+        Path appClass = classesDir.resolve("org/example/App.class");
+        assertTrue(Files.exists(appClass), "App.class should exist after 
compile");
+
+        // Simulate source change (e.g., branch switch, external update) by:
+        // 1. Modifying source file (simulates different source version)
+        // 2. Making class file appear OLDER than build start time (stale)
+        Path sourceFile = Paths.get(verifier.getBasedir(), 
"src/main/java/org/example/App.java");
+        String content = new String(Files.readAllBytes(sourceFile), "UTF-8");
+        Files.write(sourceFile, content.replace("Version A", "Version 
B").getBytes("UTF-8"));
+
+        // Backdate the class file to simulate stale artifact from previous 
build
+        FileTime oldTime = FileTime.from(Instant.now().minusSeconds(3600)); // 
1 hour ago
+        Files.setLastModifiedTime(appClass, oldTime);
+
+        // Try to build without clean (simulates developer workflow)
+        verifier.setLogFileName("../log-version-b.txt");
+        verifier.executeGoals(Arrays.asList("compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify that compiler detected source change and recompiled
+        // (class file should have new timestamp after recompile)
+        FileTime newTime = Files.getLastModifiedTime(appClass);
+        assertTrue(
+                newTime.toMillis() > oldTime.toMillis(),
+                "Compiler should have recompiled stale class (new timestamp: " 
+ newTime + ", old timestamp: " + oldTime
+                        + ")");
+    }
+}
diff --git 
a/src/test/java/org/apache/maven/buildcache/its/StaleMultimoduleArtifactTest.java
 
b/src/test/java/org/apache/maven/buildcache/its/StaleMultimoduleArtifactTest.java
new file mode 100644
index 0000000..70c5bbc
--- /dev/null
+++ 
b/src/test/java/org/apache/maven/buildcache/its/StaleMultimoduleArtifactTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.its;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.Arrays;
+
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that stale artifacts from source changes in multimodule projects are 
not cached.
+ * Verifies that the staging directory correctly preserves the full path 
structure including
+ * submodule paths relative to the multimodule root.
+ *
+ * <p>Scenario:
+ * <ol>
+ *   <li>Build multimodule project version A (creates 
module1/target/classes)</li>
+ *   <li>Simulate source change (source changes, target/classes remains 
stale)</li>
+ *   <li>Build without 'mvn clean' - should stage stale files with full path 
preservation</li>
+ *   <li>Verify staging directory structure: 
target/.maven-build-cache-stash/module1/target/classes</li>
+ * </ol>
+ */
+@IntegrationTest("src/test/projects/stale-multimodule-artifact")
+class StaleMultimoduleArtifactTest {
+
+    @Test
+    void staleMultimoduleDirectoriesCorrectlyStaged(Verifier verifier) throws 
VerificationException, IOException {
+        verifier.setAutoclean(false);
+
+        // Build version A: compile multimodule project
+        verifier.setLogFileName("../log-multimodule-version-a.txt");
+        verifier.executeGoals(Arrays.asList("clean", "compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify module1 class file was created
+        Path basedir = Paths.get(verifier.getBasedir());
+        Path module1ClassesDir = basedir.resolve("module1/target/classes");
+        Path module1Class = 
module1ClassesDir.resolve("org/example/Module1.class");
+        assertTrue(Files.exists(module1Class), "Module1.class should exist 
after compile");
+
+        // Simulate source change (e.g., branch switch, external update) by:
+        // 1. Modifying source file (simulates different source version)
+        // 2. Making class file appear OLDER than build start time (stale)
+        Path sourceFile = 
basedir.resolve("module1/src/main/java/org/example/Module1.java");
+        String content = new String(Files.readAllBytes(sourceFile), "UTF-8");
+        Files.write(sourceFile, content.replace("Version A", "Version 
B").getBytes("UTF-8"));
+
+        // Backdate the class file to simulate stale artifact from previous 
build
+        FileTime oldTime = FileTime.from(Instant.now().minusSeconds(3600)); // 
1 hour ago
+        Files.setLastModifiedTime(module1Class, oldTime);
+
+        // Build without clean (simulates developer workflow)
+        // The staleness detection should:
+        // 1. Move module1/target/classes to 
target/.maven-build-cache-stash/module1/target/classes
+        // 2. Force recompilation (Maven sees clean module1/target/)
+        // 3. After save(), restore or discard based on whether files were 
rebuilt
+        verifier.setLogFileName("../log-multimodule-version-b.txt");
+        verifier.executeGoals(Arrays.asList("compile"));
+        verifier.verifyErrorFreeLog();
+
+        // Verify that compiler detected source change and recompiled
+        // (class file should have new timestamp after recompile)
+        FileTime newTime = Files.getLastModifiedTime(module1Class);
+        assertTrue(
+                newTime.toMillis() > oldTime.toMillis(),
+                "Compiler should have recompiled stale class (new timestamp: " 
+ newTime + ", old timestamp: " + oldTime
+                        + ")");
+
+        // Verify that staging directory was cleaned up after restore
+        // After a successful build, all files should be either:
+        // 1. Restored (moved back to original location) - for unchanged files
+        // 2. Discarded (deleted from staging) - for rebuilt files
+        // So the staging directory should be empty or deleted
+        Path stagingDir = 
basedir.resolve("target/maven-build-cache-extension");
+        assertTrue(
+                !Files.exists(stagingDir),
+                "Staging directory should be deleted after all files are 
restored or discarded");
+    }
+}
diff --git a/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml 
b/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml
new file mode 100644
index 0000000..8568c4d
--- /dev/null
+++ b/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<extensions>
+    <extension>
+        <groupId>org.apache.maven.extensions</groupId>
+        <artifactId>maven-build-cache-extension</artifactId>
+        <version>${projectVersion}</version>
+    </extension>
+</extensions>
diff --git 
a/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml 
b/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml
new file mode 100644
index 0000000..6a91d57
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to You under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0 
https://maven.apache.org/xsd/build-cache-config-1.2.0.xsd";>
+    <configuration>
+        <attachedOutputs>
+            <dirNames>
+                <dirName>classes</dirName>
+                <dirName>test-classes</dirName>
+                <dirName>maven-status</dirName>
+            </dirNames>
+        </attachedOutputs>
+    </configuration>
+</cache>
diff --git a/src/test/projects/issue-393-compile-restore/app/pom.xml 
b/src/test/projects/issue-393-compile-restore/app/pom.xml
new file mode 100644
index 0000000..f732e70
--- /dev/null
+++ b/src/test/projects/issue-393-compile-restore/app/pom.xml
@@ -0,0 +1,37 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.caching.test.jpms</groupId>
+        <artifactId>issue-393-compile-restore</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>issue-393-app</artifactId>
+    <name>Issue 393 Compile Restore - App Module</name>
+    <packaging>jar</packaging>
+
+</project>
diff --git 
a/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java
 
b/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java
new file mode 100644
index 0000000..757ca82
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module org.apache.maven.caching.test.jpms.app {
+    exports org.apache.maven.caching.test.jpms.app;
+}
diff --git 
a/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java
 
b/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java
new file mode 100644
index 0000000..cbc1ace
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.caching.test.jpms.app;
+
+public final class Greeting {
+
+    private Greeting() {
+        // utility
+    }
+
+    public static String message() {
+        return "hello from module";
+    }
+}
diff --git a/src/test/projects/issue-393-compile-restore/consumer/pom.xml 
b/src/test/projects/issue-393-compile-restore/consumer/pom.xml
new file mode 100644
index 0000000..ab7a00e
--- /dev/null
+++ b/src/test/projects/issue-393-compile-restore/consumer/pom.xml
@@ -0,0 +1,64 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.caching.test.jpms</groupId>
+        <artifactId>issue-393-compile-restore</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>issue-393-consumer</artifactId>
+    <name>Issue 393 Compile Restore - Consumer Module</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>issue-393-app</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>5.10.2</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.2.5</version>
+                <configuration>
+                    <useModulePath>true</useModulePath>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git 
a/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java
 
b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java
new file mode 100644
index 0000000..76abdde
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module org.apache.maven.caching.test.jpms.consumer {
+    requires org.apache.maven.caching.test.jpms.app;
+    exports org.apache.maven.caching.test.jpms.consumer;
+}
diff --git 
a/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java
 
b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java
new file mode 100644
index 0000000..d983de1
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.caching.test.jpms.consumer;
+
+import org.apache.maven.caching.test.jpms.app.Greeting;
+
+public final class Consumer {
+
+    private Consumer() {
+        // utility
+    }
+
+    public static String message() {
+        return Greeting.message();
+    }
+}
diff --git 
a/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java
 
b/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java
new file mode 100644
index 0000000..813a973
--- /dev/null
+++ 
b/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.caching.test.jpms.consumer;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ConsumerTest {
+
+    @Test
+    void messageIsProvidedByUpstreamModule() {
+        assertEquals("hello from module", Consumer.message());
+    }
+}
diff --git a/src/test/projects/issue-393-compile-restore/pom.xml 
b/src/test/projects/issue-393-compile-restore/pom.xml
new file mode 100644
index 0000000..f504867
--- /dev/null
+++ b/src/test/projects/issue-393-compile-restore/pom.xml
@@ -0,0 +1,42 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.maven.caching.test.jpms</groupId>
+    <artifactId>issue-393-compile-restore</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>Issue 393 Compile Restore Aggregator</name>
+
+    <modules>
+        <module>app</module>
+        <module>consumer</module>
+    </modules>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.release>17</maven.compiler.release>
+    </properties>
+
+</project>
diff --git a/src/test/projects/stale-artifact/.mvn/maven-build-cache-config.xml 
b/src/test/projects/stale-artifact/.mvn/maven-build-cache-config.xml
new file mode 100644
index 0000000..c3d63ea
--- /dev/null
+++ b/src/test/projects/stale-artifact/.mvn/maven-build-cache-config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0 
https://maven.apache.org/xsd/build-cache-config-1.2.0.xsd";>
+    <configuration>
+        <attachedOutputs>
+            <dirNames>
+                <dirName>classes</dirName>
+            </dirNames>
+        </attachedOutputs>
+    </configuration>
+</cache>
diff --git a/src/test/projects/stale-artifact/pom.xml 
b/src/test/projects/stale-artifact/pom.xml
new file mode 100644
index 0000000..087372f
--- /dev/null
+++ b/src/test/projects/stale-artifact/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns="http://maven.apache.org/POM/4.0.0";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.maven.caching.test</groupId>
+    <artifactId>stale-artifact</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>org.apache.maven.extensions</groupId>
+                <artifactId>maven-build-cache-extension</artifactId>
+                <version>${projectVersion}</version>
+            </extension>
+        </extensions>
+    </build>
+
+</project>
diff --git 
a/src/test/projects/stale-artifact/src/main/java/org/example/App.java 
b/src/test/projects/stale-artifact/src/main/java/org/example/App.java
new file mode 100644
index 0000000..6ebdd9e
--- /dev/null
+++ b/src/test/projects/stale-artifact/src/main/java/org/example/App.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.example;
+
+public class App {
+    public static void main(String[] args) {
+        System.out.println("Version A");
+    }
+}
diff --git 
a/src/test/projects/stale-multimodule-artifact/.mvn/maven-build-cache-config.xml
 
b/src/test/projects/stale-multimodule-artifact/.mvn/maven-build-cache-config.xml
new file mode 100644
index 0000000..c3d63ea
--- /dev/null
+++ 
b/src/test/projects/stale-multimodule-artifact/.mvn/maven-build-cache-config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to You under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<cache xmlns="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/BUILD-CACHE-CONFIG/1.2.0 
https://maven.apache.org/xsd/build-cache-config-1.2.0.xsd";>
+    <configuration>
+        <attachedOutputs>
+            <dirNames>
+                <dirName>classes</dirName>
+            </dirNames>
+        </attachedOutputs>
+    </configuration>
+</cache>
diff --git a/src/test/projects/stale-multimodule-artifact/module1/pom.xml 
b/src/test/projects/stale-multimodule-artifact/module1/pom.xml
new file mode 100644
index 0000000..329dd9a
--- /dev/null
+++ b/src/test/projects/stale-multimodule-artifact/module1/pom.xml
@@ -0,0 +1,31 @@
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.example</groupId>
+        <artifactId>stale-multimodule-parent</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>module1</artifactId>
+</project>
diff --git 
a/src/test/projects/stale-multimodule-artifact/module1/src/main/java/org/example/Module1.java
 
b/src/test/projects/stale-multimodule-artifact/module1/src/main/java/org/example/Module1.java
new file mode 100644
index 0000000..2622957
--- /dev/null
+++ 
b/src/test/projects/stale-multimodule-artifact/module1/src/main/java/org/example/Module1.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.example;
+
+public class Module1 {
+    public static void main(String[] args) {
+        System.out.println("Module1 Version A");
+    }
+}
diff --git a/src/test/projects/stale-multimodule-artifact/pom.xml 
b/src/test/projects/stale-multimodule-artifact/pom.xml
new file mode 100644
index 0000000..4dd27ac
--- /dev/null
+++ b/src/test/projects/stale-multimodule-artifact/pom.xml
@@ -0,0 +1,47 @@
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.example</groupId>
+    <artifactId>stale-multimodule-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>org.apache.maven.extensions</groupId>
+                <artifactId>maven-build-cache-extension</artifactId>
+                <version>${projectVersion}</version>
+            </extension>
+        </extensions>
+    </build>
+
+    <modules>
+        <module>module1</module>
+    </modules>
+</project>

Reply via email to