diff --git a/runtime/test/src/test/java/com/google/apphosting/runtime/RuntimeJarDependencyClosureTest.java b/runtime/test/src/test/java/com/google/apphosting/runtime/RuntimeJarDependencyClosureTest.java
new file mode 100644
index 00000000..edda2cdb
--- /dev/null
+++ b/runtime/test/src/test/java/com/google/apphosting/runtime/RuntimeJarDependencyClosureTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed 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
+ *
+ * https://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 com.google.apphosting.runtime;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Checks that every class reachable from the App Engine classes bundled in a runtime-impl jar can
+ * be loaded from the runtime class path.
+ *
+ *
The runtime jars bundle App Engine API classes (e.g. all of {@code
+ * com.google.appengine.api.taskqueue}) that the runtime uses from its own class loader, for
+ * instance for async session persistence. When those classes start using a new library, the
+ * library must also be added to the jar's shade {@code artifactSet}, otherwise the runtime fails
+ * at run time with a {@code NoClassDefFoundError}. This test runs {@code jdeps} on the jar, with
+ * the other jars of the runtime deployment on the class path, follows every reference starting
+ * from {@code com.google.appengine} and {@code com.google.apphosting} classes, and fails on any
+ * missing class that is not a known, unused optional dependency.
+ */
+@RunWith(Parameterized.class)
+public final class RuntimeJarDependencyClosureTest {
+
+ /**
+ * Packages that are known to be referenced but not on the runtime class path, on code paths the
+ * runtime does not use. Matches the package itself and its subpackages.
+ */
+ private static final ImmutableMap KNOWN_MISSING_PACKAGES =
+ ImmutableMap.builder()
+ .put("org.antlr.runtime", "borgcron Groc schedule parser from appengine-utils")
+ .put(
+ "com.google.api.client.auth.oauth2",
+ "Cloud Datastore v1 client (CloudDatastoreV1ClientImpl, com.google.datastore.v1)")
+ .put(
+ "com.google.api.client.googleapis",
+ "Cloud Datastore v1 client (CloudDatastoreV1ClientImpl, com.google.datastore.v1)")
+ .put("io.grpc.alts", "gax-grpc DirectPath, which is not enabled")
+ .put("org.eclipse.jetty.alpn.client", "ALPN support of the Jetty HTTP client")
+ .put("javax.transaction", "Jetty JNDI UserTransaction, needs a transaction manager")
+ .put("jakarta.transaction", "Jetty JNDI UserTransaction, needs a transaction manager")
+ .buildOrThrow();
+
+ private static final ImmutableList ROOT_PREFIXES =
+ ImmutableList.of("com.google.appengine.", "com.google.apphosting.");
+
+ /** A {@code jdeps -verbose:class} dependency line: source, target, and where it was found. */
+ private static final Pattern DEPENDENCY_LINE =
+ Pattern.compile("^\\s+(\\S+)\\s+->\\s+(\\S+)\\s+(.+?)\\s*$");
+
+ @Parameters(name = "{0}")
+ public static List