diff --git a/JmxRemoteLifecycleListener.patch b/JmxRemoteLifecycleListener.patch
deleted file mode 100644
index 3145a54da87da2fc726530f0cab2f049324cb024..0000000000000000000000000000000000000000
--- a/JmxRemoteLifecycleListener.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-diff --git a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
-index f62f8d1..db19960 100644
---- a/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
-+++ b/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
-@@ -611,34 +611,28 @@ public class JmxRemoteLifecycleListener extends SSLHostConfig implements Lifecyc
- * Better to use the internal API than re-invent the wheel.
- */
- @SuppressWarnings("restriction")
-- private static class JmxRegistry extends sun.rmi.registry.RegistryImpl {
-+ private static class JmxRegistry {
- private static final long serialVersionUID = -3772054804656428217L;
- private final String jmxName;
- private final Remote jmxServer;
- public JmxRegistry(int port, RMIClientSocketFactory csf,
- RMIServerSocketFactory ssf, String jmxName, Remote jmxServer) throws RemoteException {
-- super(port, csf, ssf);
- this.jmxName = jmxName;
- this.jmxServer = jmxServer;
- }
-- @Override
- public Remote lookup(String name)
- throws RemoteException, NotBoundException {
- return (jmxName.equals(name)) ? jmxServer : null;
- }
-- @Override
- public void bind(String name, Remote obj)
- throws RemoteException, AlreadyBoundException, AccessException {
- }
-- @Override
- public void unbind(String name)
- throws RemoteException, NotBoundException, AccessException {
- }
-- @Override
- public void rebind(String name, Remote obj)
- throws RemoteException, AccessException {
- }
-- @Override
- public String[] list() throws RemoteException {
- return new String[] { jmxName };
- }
diff --git a/download b/download
index 8a56b530ad28606bab215f2f3077ffc8fd4399d6..dbcaa5b8a58c33454a6e6839b553d9ff902e4afb 100644
--- a/download
+++ b/download
@@ -1 +1 @@
-8f4934156ee00049f6dda95192b87763 tomcat-9.0.62.redhat-00014-src.zip
+ee30ffe19a1fa585c06c56a608208af0 apache-tomcat-9.0.62-src.tar.gz
diff --git a/tomcat-9.0-JDTCompiler.patch b/tomcat-9.0-JDTCompiler.patch
new file mode 100644
index 0000000000000000000000000000000000000000..edf156aaa0911cbb94a786413e25e384b440555a
--- /dev/null
+++ b/tomcat-9.0-JDTCompiler.patch
@@ -0,0 +1,24 @@
+diff -up ./java/org/apache/jasper/compiler/JDTCompiler.java ./java/org/apache/jasper/compiler/JDTCompiler.java
+index 2e361f2..277d8f4 100644
+--- java/org/apache/jasper/compiler/JDTCompiler.java
++++ java/org/apache/jasper/compiler/JDTCompiler.java
+@@ -310,7 +310,7 @@ public class JDTCompiler extends org.apache.jasper.compiler.Compiler {
+ } else if(opt.equals("15")) {
+ settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_15);
+ } else if(opt.equals("16")) {
+- settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_16);
++ settings.put(CompilerOptions.OPTION_Source, "16");
+ } else if(opt.equals("17")) {
+ // Constant not available in latest ECJ version that runs on
+ // Java 8.
+@@ -377,8 +377,8 @@ public class JDTCompiler extends org.apache.jasper.compiler.Compiler {
+ settings.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_15);
+ settings.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_15);
+ } else if(opt.equals("16")) {
+- settings.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_16);
+- settings.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_16);
++ settings.put(CompilerOptions.OPTION_TargetPlatform, "16");
++ settings.put(CompilerOptions.OPTION_Compliance, "16");
+ } else if(opt.equals("17")) {
+ // Constant not available in latest ECJ version that runs on
+ // Java 8.
diff --git a/tomcat-9.0-memory-leak.patch b/tomcat-9.0-memory-leak.patch
new file mode 100644
index 0000000000000000000000000000000000000000..00e6e562e312f1940514325e7c16078f41dfd379
--- /dev/null
+++ b/tomcat-9.0-memory-leak.patch
@@ -0,0 +1,345 @@
+diff --git a/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
+index 8746b6b..dc878c6 100644
+--- a/java/org/apache/catalina/loader/WebappClassLoaderBase.java
++++ b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
+@@ -1820,41 +1820,13 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
+ // shutting down the executor
+ boolean usingExecutor = false;
+ try {
+-
+- // Runnable wrapped by Thread
+- // "target" in Sun/Oracle JDK
+- // "runnable" in IBM JDK
+- // "action" in Apache Harmony
+- Object target = null;
+- for (String fieldName : new String[] { "target", "runnable", "action" }) {
+- try {
+- Field targetField = thread.getClass().getDeclaredField(fieldName);
+- targetField.setAccessible(true);
+- target = targetField.get(thread);
+- break;
+- } catch (NoSuchFieldException nfe) {
+- continue;
+- }
+- }
+-
+- // "java.util.concurrent" code is in public domain,
+- // so all implementations are similar including our
+- // internal fork.
+- if (target != null && target.getClass().getCanonicalName() != null &&
+- (target.getClass().getCanonicalName().equals(
+- "org.apache.tomcat.util.threads.ThreadPoolExecutor.Worker") ||
+- target.getClass().getCanonicalName().equals(
+- "java.util.concurrent.ThreadPoolExecutor.Worker"))) {
+- Field executorField = target.getClass().getDeclaredField("this$0");
+- executorField.setAccessible(true);
+- Object executor = executorField.get(target);
+- if (executor instanceof ThreadPoolExecutor) {
+- ((ThreadPoolExecutor) executor).shutdownNow();
+- usingExecutor = true;
+- } else if (executor instanceof java.util.concurrent.ThreadPoolExecutor) {
+- ((java.util.concurrent.ThreadPoolExecutor) executor).shutdownNow();
+- usingExecutor = true;
+- }
++ Object executor = JreCompat.getInstance().getExecutor(thread);
++ if (executor instanceof ThreadPoolExecutor) {
++ ((ThreadPoolExecutor) executor).shutdownNow();
++ usingExecutor = true;
++ } else if (executor instanceof java.util.concurrent.ThreadPoolExecutor) {
++ ((java.util.concurrent.ThreadPoolExecutor) executor).shutdownNow();
++ usingExecutor = true;
+ }
+ } catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) {
+ // InaccessibleObjectException is only available in Java 9+,
+@@ -2306,6 +2278,12 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
+
+
+ private void clearReferencesObjectStreamClassCaches() {
++ if (JreCompat.isJre19Available()) {
++ // The memory leak this fixes has been fixed in Java 19 onwards,
++ // 17.0.4 onwards and 11.0.16 onwards
++ // See https://bugs.openjdk.java.net/browse/JDK-8277072
++ return;
++ }
+ try {
+ Class> clazz = Class.forName("java.io.ObjectStreamClass$Caches");
+ clearCache(clazz, "localDescs");
+@@ -2333,14 +2311,19 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
+ throws ReflectiveOperationException, SecurityException, ClassCastException {
+ Field f = target.getDeclaredField(mapName);
+ f.setAccessible(true);
+- Map,?> map = (Map,?>) f.get(null);
+- Iterator> keys = map.keySet().iterator();
+- while (keys.hasNext()) {
+- Object key = keys.next();
+- if (key instanceof Reference) {
+- Object clazz = ((Reference>) key).get();
+- if (loadedByThisOrChild(clazz)) {
+- keys.remove();
++ Object map = f.get(null);
++ // Avoid trying to clear references if Tomcat is running on a JRE that
++ // includes the fix for this memory leak
++ // See https://bugs.openjdk.java.net/browse/JDK-8277072
++ if (map instanceof Map,?>) {
++ Iterator> keys = ((Map,?>) map).keySet().iterator();
++ while (keys.hasNext()) {
++ Object key = keys.next();
++ if (key instanceof Reference) {
++ Object clazz = ((Reference>) key).get();
++ if (loadedByThisOrChild(clazz)) {
++ keys.remove();
++ }
+ }
+ }
+ }
+diff --git a/java/org/apache/tomcat/util/compat/JreCompat.java b/java/org/apache/tomcat/util/compat/JreCompat.java
+index 62df145..e5df728 100644
+--- a/java/org/apache/tomcat/util/compat/JreCompat.java
++++ b/java/org/apache/tomcat/util/compat/JreCompat.java
+@@ -19,6 +19,7 @@ package org.apache.tomcat.util.compat;
+ import java.io.File;
+ import java.io.IOException;
+ import java.lang.reflect.AccessibleObject;
++import java.lang.reflect.Field;
+ import java.lang.reflect.InvocationTargetException;
+ import java.lang.reflect.Method;
+ import java.net.SocketAddress;
+@@ -45,6 +46,7 @@ public class JreCompat {
+
+ private static final JreCompat instance;
+ private static final boolean graalAvailable;
++ private static final boolean jre19Available;
+ private static final boolean jre16Available;
+ private static final boolean jre11Available;
+ private static final boolean jre9Available;
+@@ -67,18 +69,26 @@ public class JreCompat {
+
+ // This is Tomcat 9 with a minimum Java version of Java 8.
+ // Look for the highest supported JVM first
+- if (Jre16Compat.isSupported()) {
++ if (Jre19Compat.isSupported()) {
++ instance = new Jre19Compat();
++ jre9Available = true;
++ jre16Available = true;
++ jre19Available = true;
++ } else if (Jre16Compat.isSupported()) {
+ instance = new Jre16Compat();
+ jre9Available = true;
+ jre16Available = true;
++ jre19Available = false;
+ } else if (Jre9Compat.isSupported()) {
+ instance = new Jre9Compat();
+ jre9Available = true;
+ jre16Available = false;
++ jre19Available = false;
+ } else {
+ instance = new JreCompat();
+ jre9Available = false;
+ jre16Available = false;
++ jre19Available = false;
+ }
+ jre11Available = instance.jarFileRuntimeMajorVersion() >= 11;
+
+@@ -124,6 +134,9 @@ public class JreCompat {
+ return jre16Available;
+ }
+
++ public static boolean isJre19Available() {
++ return jre19Available;
++ }
+
+ // Java 8 implementation of Java 9 methods
+
+@@ -303,6 +316,8 @@ public class JreCompat {
+ }
+
+
++ // Java 8 implementations of Java 16 methods
++
+ /**
+ * Return Unix domain socket address for given path.
+ * @param path The path
+@@ -329,4 +344,63 @@ public class JreCompat {
+ public SocketChannel openUnixDomainSocketChannel() {
+ throw new UnsupportedOperationException(sm.getString("jreCompat.noUnixDomainSocket"));
+ }
++
++
++ // Java 8 implementations of Java 19 methods
++
++ /**
++ * Obtains the executor, if any, used to create the provided thread.
++ *
++ * @param thread The thread to examine
++ *
++ * @return The executor, if any, that created the provided thread
++ *
++ * @throws NoSuchFieldException
++ * If a field used via reflection to obtain the executor cannot
++ * be found
++ * @throws SecurityException
++ * If a security exception occurs while trying to identify the
++ * executor
++ * @throws IllegalArgumentException
++ * If the instance object does not match the class of the field
++ * when obtaining a field value via reflection
++ * @throws IllegalAccessException
++ * If a field is not accessible due to access restrictions
++ */
++ public Object getExecutor(Thread thread)
++ throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
++
++ Object result = null;
++
++ // Runnable wrapped by Thread
++ // "target" in Sun/Oracle JDK
++ // "runnable" in IBM JDK
++ // "action" in Apache Harmony
++ Object target = null;
++ for (String fieldName : new String[] { "target", "runnable", "action" }) {
++ try {
++ Field targetField = thread.getClass().getDeclaredField(fieldName);
++ targetField.setAccessible(true);
++ target = targetField.get(thread);
++ break;
++ } catch (NoSuchFieldException nfe) {
++ continue;
++ }
++ }
++
++ // "java.util.concurrent" code is in public domain,
++ // so all implementations are similar including our
++ // internal fork.
++ if (target != null && target.getClass().getCanonicalName() != null &&
++ (target.getClass().getCanonicalName().equals(
++ "org.apache.tomcat.util.threads.ThreadPoolExecutor.Worker") ||
++ target.getClass().getCanonicalName().equals(
++ "java.util.concurrent.ThreadPoolExecutor.Worker"))) {
++ Field executorField = target.getClass().getDeclaredField("this$0");
++ executorField.setAccessible(true);
++ result = executorField.get(target);
++ }
++
++ return result;
++ }
+ }
+diff --git a/java/org/apache/tomcat/util/compat/LocalStrings.properties b/java/org/apache/tomcat/util/compat/LocalStrings.properties
+index 79427da..c4c2f7d 100644
+--- a/java/org/apache/tomcat/util/compat/LocalStrings.properties
++++ b/java/org/apache/tomcat/util/compat/LocalStrings.properties
+@@ -16,6 +16,8 @@
+ jre16Compat.javaPre16=Class not found so assuming code is running on a pre-Java 16 JVM
+ jre16Compat.unexpected=Failed to create references to Java 16 classes and methods
+
++jre19Compat.javaPre19=Class not found so assuming code is running on a pre-Java 19 JVM
++
+ jre9Compat.invalidModuleUri=The module URI provided [{0}] could not be converted to a URL for the JarScanner to process
+ jre9Compat.javaPre9=Class not found so assuming code is running on a pre-Java 9 JVM
+ jre9Compat.unexpected=Failed to create references to Java 9 classes and methods
+diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
+index d118196..42dfe38 100644
+--- a/webapps/docs/config/context.xml
++++ b/webapps/docs/config/context.xml
+@@ -769,7 +769,11 @@
+ therefore requires that the command line option
+ -XaddExports:java.base/java.io=ALL-UNNAMED
is set
+ when running on Java 9 and above. If not specified, the default value of
+- true
will be used.
true
will be used.
++ The memory leak associated with ObjectStreamClass
has
++ been fixed in Java 19 onwards, Java 17.0.4 onwards and Java 11.0.16
++ onwards. The check will be disabled when running on a version
++ of Java that contains the fix.