diff a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
--- a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -34,13 +34,15 @@
 import java.lang.module.ModuleReference;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Supplier;
 import java.util.jar.JarEntry;
@@ -50,11 +52,10 @@
 
 import jdk.internal.jmod.JmodFile;
 import jdk.internal.module.ModuleHashes.HashSupplier;
 import sun.net.www.ParseUtil;
 
-
 /**
  * A factory for creating ModuleReference implementations where the modules are
  * packaged as modular JAR file, JMOD files or where the modules are exploded
  * on the file system.
  */
@@ -120,12 +121,13 @@
     /**
      * Creates a ModuleReference to a possibly-patched exploded module.
      */
     static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs,
                                              ModulePatcher patcher,
+                                             boolean previewMode,
                                              Path dir) {
-        Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir);
+        Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir, previewMode);
         return newModule(attrs, dir.toUri(), supplier, patcher, null);
     }
 
 
     /**
@@ -364,27 +366,43 @@
     /**
      * A ModuleReader for an exploded module.
      */
     static class ExplodedModuleReader implements ModuleReader {
         private final Path dir;
+        private final Path previewDir;
         private volatile boolean closed;
 
-        ExplodedModuleReader(Path dir) {
+        ExplodedModuleReader(Path dir, boolean previewMode) {
             this.dir = dir;
+            Path path = dir.resolve("META-INF", "preview");
+            this.previewDir = (previewMode && Files.isDirectory(path)) ? path : null;
         }
 
         /**
          * Throws IOException if the module reader is closed;
          */
         private void ensureOpen() throws IOException {
             if (closed) throw new IOException("ModuleReader is closed");
         }
 
+        private Path toFilePath(String name) throws IOException {
+            if (previewDir != null) {
+                if (isPreviewEntry(name)) {
+                    return null;
+                }
+                Path previewPath = Resources.toFilePath(previewDir, name);
+                if (previewPath != null && Files.exists(previewPath)) {
+                    return previewPath;
+                }
+            }
+            return Resources.toFilePath(dir, name);
+        }
+
         @Override
         public Optional<URI> find(String name) throws IOException {
             ensureOpen();
-            Path path = Resources.toFilePath(dir, name);
+            Path path = toFilePath(name);
             if (path != null) {
                 try {
                     return Optional.of(path.toUri());
                 } catch (IOError e) {
                     throw (IOException) e.getCause();
@@ -395,39 +413,59 @@
         }
 
         @Override
         public Optional<InputStream> open(String name) throws IOException {
             ensureOpen();
-            Path path = Resources.toFilePath(dir, name);
+            Path path = toFilePath(name);
             if (path != null) {
                 return Optional.of(Files.newInputStream(path));
             } else {
                 return Optional.empty();
             }
         }
 
         @Override
         public Optional<ByteBuffer> read(String name) throws IOException {
             ensureOpen();
-            Path path = Resources.toFilePath(dir, name);
+            Path path = toFilePath(name);
             if (path != null) {
                 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
             } else {
                 return Optional.empty();
             }
         }
 
         @Override
         public Stream<String> list() throws IOException {
             ensureOpen();
-            return Files.walk(dir, Integer.MAX_VALUE)
-                        .map(f -> Resources.toResourceName(dir, f))
-                        .filter(s -> s.length() > 0);
+            LinkedHashSet<String> names = new LinkedHashSet<>();
+            collectNames(dir, names);
+            if (previewDir != null) {
+                collectNames(previewDir, names);
+            }
+            return names.stream();
+        }
+
+        private void collectNames(Path dir, Set<String> dest) throws IOException {
+            try (Stream<Path> files = Files.walk(dir, Integer.MAX_VALUE)) {
+                files.map(f -> Resources.toResourceName(dir, f))
+                        .filter(s -> !s.isEmpty())
+                        .filter(s -> previewDir == null || !isPreviewEntry(s))
+                        .forEach(dest::add);
+            }
         }
 
         @Override
         public void close() {
             closed = true;
         }
-    }
 
+        // Names do not have a leading '/'.
+        private static final String PREVIEW_PREFIX = "META-INF/preview";
+
+        private static boolean isPreviewEntry(String name) {
+            return name.startsWith(PREVIEW_PREFIX) &&
+                    (name.length() == PREVIEW_PREFIX.length()
+                            || name.charAt(PREVIEW_PREFIX.length()) == '/');
+        }
+    }
 }
