[575089] Add Tomcat 10.1.x support to WTP Server Tools

Note: Version bump not included.

Change-Id: I6f02bb1cfedd3e53d246fdf245aebcc79210d628
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/AddTomcat.md b/plugins/org.eclipse.jst.server.tomcat.core/AddTomcat.md
index 29272a4..72a4ab9 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/AddTomcat.md
+++ b/plugins/org.eclipse.jst.server.tomcat.core/AddTomcat.md
@@ -8,10 +8,11 @@
 
 1. Modify **plugin.properties** to add properties strings for the new version of Tomcat. Use existing properties as a template and make appropriate changes.  Update the **runtimeTypeTomcat???Description** value to include the new specifications that this version supports.
 2. Modify **plugin.xml** to add new configuration elements. Add new elements using existing ones as a template and make appropriate changes. Be sure to update **id** values appropriately.
-    1. Add a new **runtimeType** to the **org.eclipse.wst.server.core.runtimeTypes** extension. In the new runtimeType, the **version** list in the **moduleType** element should include the new version that this Tomcat supports, provided Eclipse has support for it. If not yet supported, it may need to be added at a later time. To check, locate the *webtools.javaee* Git repository. Examine the *plugin.xml* for the *plugins/org.eclipse.jst.jee.web project*.
-    2. Add a new **serverType** to the **org.eclipse.wst.server.core.serverTypes** extension. Besure to update the **runtimeTypeId** to match the id of the runtimeType added in step 1.
-    3. Add a new **runtime-component-version** to the **org.eclipse.wst.common.project.facet.core.runtimes** extension. Also add a **supported** element specifying the new version for its **runtime-component**.
-    4. Add a new **runtimeFacetMapping** to the **org.eclipse.jst.server.core.runtimeFacetMappings** extension.
+    1. Add a new **runtimeType** to the **org.eclipse.wst.server.core.runtimeTypes** extension. In the new runtimeType, the **version** list in the **moduleType** element should include the new version that this Tomcat supports, provided Eclipse has support for it. If not yet supported, it may need to be added at a later time. To check, locate the *webtools.javaee* Git repository. Examine the *plugin.xml* for the *plugins/org.eclipse.jst.jee.web project*. In the **org.eclipse.wst.common.project.facet.core.facets** extension declarations, the *jst.web* facets show the supported versions. Note: It appears that adding the appropriate versions before they are supported in Eclipse is okay.
+    2. Add a new **serverType** to the **org.eclipse.wst.server.core.serverTypes** extension. Be sure to update the **runtimeTypeId** to match the id of the runtimeType added in step 1.
+    3. Add a new **runtime-component-version** element to the **org.eclipse.wst.common.project.facet.core.runtimes** extension specifying the new Tomcat version. Also add a **supported** element specifying the new version for its **runtime-component**. Under this supported element, add appropriate versions to the list of supported facet versions determined in step 1.
+    4. Add a new **runtimeFacetMapping** to the **org.eclipse.jst.server.core.runtimeFacetMappings** extension, specifying the appropriate runtimeTypeId and version.
+    5. At some point add a pair of **runtime** entries to the **org.eclipse.wst.server.core.installableRuntimes** extension using existing entries as a template. See the end of this document for some commands to help with the parameters. It is best to add these after the Tomcat version is having official releases as the specific version will become obsolete very quickly prior to that.
 3. Modify **Messages.java** and **Messages.properties** to add strings for **errorJRETomcat???** and **errorSpec???**. Update the content of the strings as needed per Tomcat documention, such as the minimum version of the JRE that can be used.
 4. Modify **TomcatPlugin.java** to add a new **TOMCAT_???** string constant. Then modify its **getTomcatVersionHandler()** method to return the appropriate **Tomcat???Handler** which will be created next.
 5. Create a new **Tomcat???Handler** class by copying an existing one. 
@@ -26,19 +27,19 @@
 9. Modify **TomcatRuntimeLocator.java** to update the **runtimeTypes** string array to add the runtime id of the new Tomcat version.
 10. Modify **TomcatServer.java** to update the **getTomcatConfiguration()** and **importRuntimeConfiguration()** methods to instantiate a **Tomcat???Configuration** when the **id** matches the new Tomcat version.
 11. Modify **TomcatVersionHelper.java** to support the new Tomcat version.
-    1. Update the initialization of the **versionStringMap** to add an entry that maps the new **TomcatPlugin.TOMCAT_???** to a version string.
+    1. Update the initialization of the **versionStringMap** to add an entry that maps the new **TomcatPlugin.TOMCAT_???** to a version string prefix.
     2. Update the **updateContextsToServeDirectly()** to add an **isTomcat??** boolean and **else if** code to instantiate a **Tomcat???PublishModuleVisitor** when that boolean is true.
     3. Update the **checkCatalinaVersion()** method to include checking the **TomcatPlugin.TOMCAT_???** matching the **serverType**.
 12. Modify **TomcatRuntimeClasspathProvider.java** to support the new Tomcat version.
-    1. Update the **resolveClasspathContainer()** method appropriately. Likely just add an additional runtimeId check to the first **else if** in the method.
+    1. Update the **resolveClasspathContainer()** method appropriately. Likely just add an additional runtimeId check to the first **else if** in the method. Also update the **if** statement to set an appropriate **eeVersion** for the new Tomcat version, if appropriate.  Also ensure the **eeVersion** is set to the latest Jakarta EE when exceptions are thrown.
     2. Modify the **getTomcatJavadocLocation()** method to return the appropriate URL for the new and prior version of Tomcat.
-    3. Modify the **getEEJavadocLocation()** method to return a URL appropriate for Javadoc related to the version of specifications for the new Tomcat version.
+    3. Modify the **getStandardsJavadocLocation()** method to return a URL appropriate for Javadoc related to the version of specifications for the new Tomcat version. Navigate from https://projects.eclipse.org/projects/ee4j.jakartaee-platform to find the subproject versions of the Jakarta EE version supported by the new Tomcat version, if needed.
 
 ### Modifications to plugins/org.eclipse.jst.server.tomcat.ui
 
 1. Modify **plugin.xml** to add new configuration elements.
-    1. Add a new **image** to the **org.eclipse.wst.server.ui.serverImages** extension for new runtime and server ids.
-    2. Add a new **fragment** to the **org.eclipse.wst.server.ui.wizardFragments** extension.
+    1. Add two new **image** elements to the **org.eclipse.wst.server.ui.serverImages** extension using an existing elements as a template. Update the **id** and **typeIds** appropriately for the new Tomcat version.
+    2. Add a new **fragment** element to the **org.eclipse.wst.server.ui.wizardFragments** extension using an existing element as a template. Update the **id** and **typeIds** appropriately for the new Tomcat version.
 
 ### Determining values for installable runtimes:
     1. For ostype win32:  filecount = unzip -l apache-tomcat-10.0.13.zip|grep -v '\/$'|grep 'apache-tomcat-'|grep -v ^Archive|wc -l
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/plugin.properties b/plugins/org.eclipse.jst.server.tomcat.core/plugin.properties
index fa381f9..efab75f 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/plugin.properties
+++ b/plugins/org.eclipse.jst.server.tomcat.core/plugin.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2004, 2020 IBM Corporation and others.
+# Copyright (c) 2004, 2022 IBM Corporation and others.
 # All rights reserved. This program and the accompanying materials
 # are made available under the terms of the Eclipse Public License 2.0
 # which accompanies this distribution, and is available at
@@ -37,6 +37,8 @@
 runtimeTypeTomcat90Description=Apache Tomcat v9.0 supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, and 8 Web modules.
 runtimeTypeTomcat100Label=Apache Tomcat v10.0
 runtimeTypeTomcat100Description=Apache Tomcat v10.0 supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, 8 and Jakarta EE 9 Web modules.
+runtimeTypeTomcat101Label=Apache Tomcat v10.1
+runtimeTypeTomcat101Description=Apache Tomcat v10.1 supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, 8 and Jakarta EE 9 and 10 Web modules.
 
 # --------------- Servers ---------------
 tomcat32ServerType=Tomcat v3.2 Server
@@ -72,6 +74,9 @@
 tomcat100ServerType=Tomcat v10.0 Server
 tomcat100ServerDescription=Publishes and runs J2EE, Java EE, and Jakarta EE Web projects and server configurations to a local Tomcat server.
 
+tomcat101ServerType=Tomcat v10.1 Server
+tomcat101ServerDescription=Publishes and runs J2EE, Java EE, and Jakarta EE Web projects and server configurations to a local Tomcat server.
+
 tomcatLaunchConfigurationType=Apache Tomcat
 
 # --------------- General ---------------
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/plugin.xml b/plugins/org.eclipse.jst.server.tomcat.core/plugin.xml
index ea5048c..3650bb0 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/plugin.xml
+++ b/plugins/org.eclipse.jst.server.tomcat.core/plugin.xml
@@ -163,6 +163,20 @@
          types="jst.utility"
          versions="1.0"/>
     </runtimeType>
+    <runtimeType
+       id="org.eclipse.jst.server.tomcat.runtime.101"
+       name="%runtimeTypeTomcat101Label"
+       description="%runtimeTypeTomcat101Description"
+       vendor="%apache"
+       version="10.1"
+       class="org.eclipse.jst.server.tomcat.core.internal.TomcatRuntime">
+       <moduleType
+         types="jst.web"
+         versions="2.2, 2.3, 2.4, 2.5, 3.0, 3.1, 4.0, 5.0, 6.0"/>
+       <moduleType
+         types="jst.utility"
+         versions="1.0"/>
+    </runtimeType>
   </extension>
 
   <extension point="org.eclipse.wst.server.core.runtimeLocators">
@@ -345,6 +359,21 @@
        class="org.eclipse.jst.server.tomcat.core.internal.TomcatServer"
        behaviourClass="org.eclipse.jst.server.tomcat.core.internal.TomcatServerBehaviour">
      </serverType>
+     <serverType
+       id="org.eclipse.jst.server.tomcat.101"
+       name="%tomcat101ServerType"
+       description="%tomcat101ServerDescription"
+       supportsRemoteHosts="false"
+       runtime="true"
+       startTimeout="60000"
+       stopTimeout="15000"
+       initialState="stopped"
+       hasConfiguration="true"
+       launchConfigId="org.eclipse.jst.server.tomcat.core.launchConfigurationType"
+       runtimeTypeId="org.eclipse.jst.server.tomcat.runtime.101"
+       class="org.eclipse.jst.server.tomcat.core.internal.TomcatServer"
+       behaviourClass="org.eclipse.jst.server.tomcat.core.internal.TomcatServerBehaviour">
+     </serverType>
   </extension>
 
   <extension point="org.eclipse.wst.server.core.serverLocators">
@@ -432,6 +461,10 @@
        type="org.eclipse.jst.server.tomcat"
        version="10.0"/>
 
+    <runtime-component-version
+       type="org.eclipse.jst.server.tomcat"
+       version="10.1"/>
+
     <adapter>
       <runtime-component
          id="org.eclipse.jst.server.tomcat"/>
@@ -582,7 +615,22 @@
          version="2.2,2.3,2.4,2.5,3.0,3.1,4.0,5.0"/>
       <facet
          id="jst.webfragment"
-         version="3.0,3.1,4.0"/>
+         version="3.0,3.1,4.0,5.0"/>
+      <facet
+         id="jst.utility"
+         version="1.0"/>
+    </supported>
+
+    <supported>
+      <runtime-component
+         id="org.eclipse.jst.server.tomcat"
+         version="10.1"/>
+      <facet
+         id="jst.web"
+         version="2.2,2.3,2.4,2.5,3.0,3.1,4.0,5.0,6.0"/>
+      <facet
+         id="jst.webfragment"
+         version="3.0,3.1,4.0,5.0,6.0"/>
       <facet
          id="jst.utility"
          version="1.0"/>
@@ -641,6 +689,10 @@
       runtimeTypeId="org.eclipse.jst.server.tomcat.runtime.100"
       runtime-component="org.eclipse.jst.server.tomcat"
       version="10.0"/>
+    <runtimeFacetMapping
+      runtimeTypeId="org.eclipse.jst.server.tomcat.runtime.101"
+      runtime-component="org.eclipse.jst.server.tomcat"
+      version="10.1"/>
   </extension>
 
   <extension point="org.eclipse.wst.server.core.installableRuntimes">
@@ -810,5 +862,21 @@
         archiveSize="12525287"
         fileCount="636"
         os="win32"/>
+    <runtime
+        id="org.eclipse.jst.server.tomcat.runtime.101"
+        licenseUrl="https://www.apache.org/licenses/LICENSE-2.0.txt"
+        archiveUrl="https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.1/bin/apache-tomcat-10.1.1.tar.gz"
+        archivePath="apache-tomcat-10.1.1"
+        archiveSize="11899518"
+        fileCount="632"
+        os="linux,macosx"/>
+    <runtime
+        id="org.eclipse.jst.server.tomcat.runtime.101"
+        licenseUrl="https://www.apache.org/licenses/LICENSE-2.0.txt"
+        archiveUrl="https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.1/bin/apache-tomcat-10.1.1.zip"
+        archivePath="apache-tomcat-10.1.1"
+        archiveSize="12451802"
+        fileCount="632"
+        os="win32"/>
   </extension>
 </plugin>
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
index 175f9af..101a507 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
@@ -37,6 +37,7 @@
 	public static String errorJRETomcat85;
 	public static String errorJRETomcat90;
 	public static String errorJRETomcat100;
+	public static String errorJRETomcat101;
 	public static String warningJRE;
 	public static String warningCantReadConfig;
 	public static String target32runtime;
@@ -64,6 +65,7 @@
 	public static String errorSpec85;
 	public static String errorSpec90;
 	public static String errorSpec100;
+	public static String errorSpec101;
 	public static String portServer;
 	public static String runtimeDirPrepared;
 	public static String publishConfigurationTask;
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
index e3571fe..798333f 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
@@ -75,6 +75,7 @@
 errorJRETomcat85=Tomcat version 8.5 requires Java SE 7 or later.  Change the JRE to one that meets this requirement.
 errorJRETomcat90=Tomcat version 9.0 requires Java SE 8 or later.  Change the JRE to one that meets this requirement.
 errorJRETomcat100=Tomcat version 10.0 requires Java SE 8 or later.  Change the JRE to one that meets this requirement.
+errorJRETomcat101=Tomcat version 10.1 requires Java SE 11 or later.  Change the JRE to one that meets this requirement.
 errorPortInvalid=The server cannot be started because one or more of the ports are invalid. Open the server editor and correct the invalid ports.
 errorPortInUse=Port {0} required by {1} is already in use. The server may already be running in another process, or a system process may be using the port. \
   To start this server you will need to stop the other process or change the port number(s).
@@ -103,6 +104,7 @@
 errorSpec85=Tomcat version 8.5 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, and 7 Web modules
 errorSpec90=Tomcat version 9.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, and 8 Web modules
 errorSpec100=Tomcat version 10.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, 8, and Jakarta EE 9 Web modules
+errorSpec101=Tomcat version 10.1 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5, 6, 7, 8, and Jakarta EE 9 and 10 Web modules
 errorDuplicateContextRoot=Two or more Web modules defined in the configuration have the same context root ({0}). \
   To start this server you will need to remove the duplicate(s).
 errorCouldNotLoadContextXml=Could not load the context configuration for the {0} context due to a syntax error or other exception.
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Configuration.java
new file mode 100644
index 0000000..a1326b2
--- /dev/null
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Configuration.java
@@ -0,0 +1,714 @@
+/**********************************************************************
+ * Copyright (c) 2020, 2022 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors:
+ *    IBM Corporation - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.jst.server.tomcat.core.internal;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jst.server.tomcat.core.internal.xml.Factory;
+import org.eclipse.jst.server.tomcat.core.internal.xml.XMLUtil;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Connector;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Context;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Listener;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Server;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.ServerInstance;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Service;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.server.core.ServerPort;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+/**
+ * Tomcat v10.1 server configuration.
+ */
+public class Tomcat101Configuration extends TomcatConfiguration {
+	protected static final String DEFAULT_SERVICE = "Catalina";
+	protected static final String EOL = System.getProperty("line.separator");
+	protected Server server;
+	protected ServerInstance serverInstance;
+	protected Factory serverFactory;
+	protected boolean isServerDirty;
+
+	protected WebAppDocument webAppDocument;
+
+	protected Document contextDocument;
+
+	protected Document tomcatUsersDocument;
+
+	protected String policyFile;
+
+	protected String propertiesFile;
+	
+	protected static final Map<String, String> protocolHandlerMap = new HashMap<String, String>();
+	static {
+		protocolHandlerMap.put("org.apache.coyote.http11.Http11Protocol", "HTTP/1.1");
+		protocolHandlerMap.put("org.apache.coyote.http11.Http11NioProtocol", "HTTP/1.1");
+		protocolHandlerMap.put("org.apache.coyote.http11.Http11AprProtocol", "HTTP/1.1");
+		protocolHandlerMap.put("org.apache.coyote.ajp.AjpAprProtocol", "AJP/1.3");
+		protocolHandlerMap.put("org.apache.jk.server.JkCoyoteHandler", "AJP/1.3");
+	}
+	
+	/**
+	 * Tomcat101Configuration constructor.
+	 * 
+	 * @param path a path
+	 */
+	public Tomcat101Configuration(IFolder path) {
+		super(path);
+	}
+
+	/**
+	 * Return the port number.
+	 * @return int
+	 */
+	public ServerPort getMainPort() {
+		Iterator iterator = getServerPorts().iterator();
+		while (iterator.hasNext()) {
+			ServerPort port = (ServerPort) iterator.next();
+			// Return only an HTTP port from the selected Service
+			if (port.getProtocol().toLowerCase().equals("http") && port.getId().indexOf('/') < 0)
+				return port;
+		}
+		return null;
+	}
+	
+	/**
+	 * Returns the mime mappings.
+	 * @return java.util.List
+	 */
+	public List getMimeMappings() {
+		return webAppDocument.getMimeMappings();
+	}
+
+	/**
+	 * Returns a list of ServerPorts that this configuration uses.
+	 *
+	 * @return java.util.List
+	 */
+	public List getServerPorts() {
+		List<ServerPort> ports = new ArrayList<ServerPort>();
+	
+		// first add server port
+		try {
+			int port = Integer.parseInt(server.getPort());
+			ports.add(new ServerPort("server", Messages.portServer, port, "TCPIP"));
+		} catch (Exception e) {
+			// ignore
+		}
+	
+		// add connectors
+		try {
+			String instanceServiceName = serverInstance.getService().getName();
+			int size = server.getServiceCount();
+			for (int i = 0; i < size; i++) {
+				Service service = server.getService(i);
+				int size2 = service.getConnectorCount();
+				for (int j = 0; j < size2; j++) {
+					Connector connector = service.getConnector(j);
+					String name = "HTTP/1.1";
+					String protocol2 = "HTTP";
+					boolean advanced = true;
+					String[] contentTypes = null;
+					int port = -1;
+					try {
+						port = Integer.parseInt(connector.getPort());
+					} catch (Exception e) {
+						// ignore
+					}
+					String protocol = connector.getProtocol();
+					if (protocol != null && protocol.length() > 0) {
+						if (protocol.startsWith("HTTP")) {
+							name = protocol;
+						}
+						else if (protocol.startsWith("AJP")) {
+							name = protocol;
+							protocol2 = "AJP"; 
+						}
+						else {
+							// Get Tomcat equivalent name if protocol handler class specified
+							name = protocolHandlerMap.get(protocol);
+							if (name != null) {
+								// Prepare simple protocol string for ServerPort protocol
+								int index = name.indexOf('/');
+								if (index > 0)
+									protocol2 = name.substring(0, index);
+								else
+									protocol2 = name;
+							}
+							// Specified protocol is unknown, just use as is
+							else {
+								name = protocol;
+								protocol2 = protocol;
+							}
+						}
+					}
+					if (protocol2.toLowerCase().equals("http"))
+						contentTypes = new String[] { "web", "webservices" };
+					String secure = connector.getSecure();
+					if (secure != null && secure.length() > 0) {
+						name = "SSL";
+						protocol2 = "SSL";
+					} else
+						advanced = false;
+					String portId;
+					if (instanceServiceName != null && instanceServiceName.equals(service.getName()))
+						portId = Integer.toString(j);
+					else
+						portId = i +"/" + j;
+					ports.add(new ServerPort(portId, name, port, protocol2, contentTypes, advanced));
+				}
+			}
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error getting server ports", e);
+		}
+		return ports;
+	}
+	
+	/**
+	 * Return a list of the web modules in this server.
+	 * @return java.util.List
+	 */
+	public List getWebModules() {
+		List<WebModule> list = new ArrayList<WebModule>();
+	
+		try {
+			Context [] contexts = serverInstance.getContexts();
+			if (contexts != null) {
+				for (int i = 0; i < contexts.length; i++) {
+					Context context = contexts[i];
+					String reload = context.getReloadable();
+					if (reload == null)
+						reload = "false";
+					WebModule module = new WebModule(context.getPath(), 
+						context.getDocBase(), context.getSource(),
+						reload.equalsIgnoreCase("true") ? true : false);
+					list.add(module);
+				}
+			}
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error getting project refs", e);
+		}
+		return list;
+	}
+	
+	/**
+	 * @see TomcatConfiguration#getServerWorkDirectory(IPath)
+	 */
+	public IPath getServerWorkDirectory(IPath basePath) {
+		return serverInstance.getHostWorkDirectory(basePath);
+	}
+
+	/**
+	 * @see TomcatConfiguration#getContextWorkDirectory(IPath, ITomcatWebModule)
+	 */
+	public IPath getContextWorkDirectory(IPath basePath, ITomcatWebModule module) {
+		Context context = serverInstance.getContext(module.getPath());
+		if (context != null)
+			return serverInstance.getContextWorkDirectory(basePath, context);
+		
+		return null;
+	}
+
+	/**
+	 * @see TomcatConfiguration#load(IPath, IProgressMonitor)
+	 */
+	public void load(IPath path, IProgressMonitor monitor) throws CoreException {
+		try {
+			monitor = ProgressUtil.getMonitorFor(monitor);
+			monitor.beginTask(Messages.loadingTask, 7);
+			
+			// check for catalina.policy to verify that this is a v4.0 or later config
+			InputStream in = new FileInputStream(path.append("catalina.policy").toFile());
+			in.read();
+			in.close();
+			monitor.worked(1);
+
+			serverFactory = new Factory();
+			serverFactory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
+			server = (Server) serverFactory.loadDocument(new FileInputStream(path.append("server.xml").toFile()));
+			serverInstance = new ServerInstance(server, null, null);
+			monitor.worked(1);
+
+			webAppDocument = new WebAppDocument(path.append("web.xml"));
+			monitor.worked(1);
+			
+			File file = path.append("context.xml").toFile();
+			if (file.exists())
+				contextDocument = XMLUtil.getDocumentBuilder().parse(new InputSource(new FileInputStream(file)));
+			monitor.worked(1);
+			
+			tomcatUsersDocument = XMLUtil.getDocumentBuilder().parse(new InputSource(new FileInputStream(path.append("tomcat-users.xml").toFile())));
+			monitor.worked(1);
+			
+			// load policy file
+			policyFile = TomcatVersionHelper.getFileContents(new FileInputStream(path.append("catalina.policy").toFile()));
+			monitor.worked(1);
+
+			// load properties file
+			file = path.append("catalina.properties").toFile();
+			if (file.exists())
+				propertiesFile = TomcatVersionHelper.getFileContents(new FileInputStream(file));
+			else
+				propertiesFile = null;
+			monitor.worked(1);
+			
+			if (monitor.isCanceled())
+				return;
+			monitor.done();
+		} catch (Exception e) {
+			TomcatPlugin.log(e);
+			Trace.trace(Trace.WARNING, "Could not load Tomcat v10.1 configuration from " + path.toOSString() + ": " + e.getMessage());
+			throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorCouldNotLoadConfiguration, path.toOSString()), e));
+		}
+	}
+
+	/**
+	 * @see TomcatConfiguration#importFromPath(IPath, boolean, IProgressMonitor)
+	 */
+	public void importFromPath(IPath path, boolean isTestEnv, IProgressMonitor monitor) throws CoreException {
+		load(path, monitor);
+		
+		// for test environment, remove existing contexts since a separate
+		// catalina.base will be used
+		if (isTestEnv) {
+			while (serverInstance.removeContext(0)) {
+				// no-op
+			}
+		}
+	}
+
+	/**
+	 * @see TomcatConfiguration#load(IFolder, IProgressMonitor)
+	 */
+	public void load(IFolder folder, IProgressMonitor monitor) throws CoreException {
+		try {
+			monitor = ProgressUtil.getMonitorFor(monitor);
+			monitor.beginTask(Messages.loadingTask, 1200);
+	
+			// check for catalina.policy to verify that this is a v4.0 or later config
+			IFile file = folder.getFile("catalina.policy");
+			if (!file.exists())
+				throw new CoreException(new Status(IStatus.WARNING, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorCouldNotLoadConfigurationMissingFile, folder.getFullPath().toOSString(), file.getFullPath().toOSString()), null));
+	
+			// load server.xml
+			file = folder.getFile("server.xml");
+			InputStream in = file.getContents();
+			serverFactory = new Factory();
+			serverFactory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
+			server = (Server) serverFactory.loadDocument(in);
+			serverInstance = new ServerInstance(server, null, null);
+			monitor.worked(200);
+	
+			// load web.xml
+			file = folder.getFile("web.xml");
+			webAppDocument = new WebAppDocument(file);
+			monitor.worked(200);
+	
+			// load context.xml
+			file = folder.getFile("context.xml");
+			if (file.exists()) {
+				in = file.getContents();
+				contextDocument = XMLUtil.getDocumentBuilder().parse(new InputSource(in));
+			}
+			else
+				contextDocument = null;
+			monitor.worked(200);
+		
+			// load tomcat-users.xml
+			file = folder.getFile("tomcat-users.xml");
+			in = file.getContents();
+			
+			tomcatUsersDocument = XMLUtil.getDocumentBuilder().parse(new InputSource(in));
+			monitor.worked(200);
+		
+			// load catalina.policy
+			file = folder.getFile("catalina.policy");
+			in = file.getContents();
+			policyFile = TomcatVersionHelper.getFileContents(in);
+			monitor.worked(200);
+	
+			// load catalina.properties
+			file = folder.getFile("catalina.properties");
+			if (file.exists()) {
+				in = file.getContents();
+				propertiesFile = TomcatVersionHelper.getFileContents(in);
+			}
+			else
+				propertiesFile = null;
+			monitor.worked(200);
+			
+			if (monitor.isCanceled())
+				throw new Exception("Cancelled");
+			monitor.done();
+		} catch (Exception e) {
+			TomcatPlugin.log(e);
+			Trace.trace(Trace.WARNING, "Could not reload Tomcat v10.1 configuration from: " + folder.getFullPath() + ": " + e.getMessage());
+			throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorCouldNotLoadConfiguration, folder.getFullPath().toOSString()), e));
+		}
+	}
+
+	/**
+	 * Save to the given directory.
+	 * @param path a path
+	 * @param forceDirty boolean
+	 * @param monitor a progress monitor
+	 * @exception CoreException
+	 */
+	protected void save(IPath path, boolean forceDirty, IProgressMonitor monitor) throws CoreException {
+		try {
+			monitor = ProgressUtil.getMonitorFor(monitor);
+			monitor.beginTask(Messages.savingTask, 5);
+			
+			// make sure directory exists
+			if (!path.toFile().exists()) {
+				forceDirty = true;
+				path.toFile().mkdir();
+			}
+			monitor.worked(1);
+			
+			// save files
+			if (forceDirty || isServerDirty) {
+				serverFactory.save(path.append("server.xml").toOSString());
+				isServerDirty = false;
+			}
+			monitor.worked(1);
+			
+			webAppDocument.save(path.append("web.xml").toOSString(), forceDirty);
+			monitor.worked(1);
+			
+			if (forceDirty && contextDocument != null)
+				XMLUtil.save(path.append("context.xml").toOSString(), contextDocument);
+			monitor.worked(1);
+			
+			if (forceDirty)
+				XMLUtil.save(path.append("tomcat-users.xml").toOSString(), tomcatUsersDocument);
+			monitor.worked(1);
+			
+			if (forceDirty) {
+				BufferedWriter bw = new BufferedWriter(new FileWriter(path.append("catalina.policy").toFile()));
+				bw.write(policyFile);
+				bw.close();
+			}
+			monitor.worked(1);
+			if (propertiesFile != null && forceDirty) {
+				BufferedWriter bw = new BufferedWriter(new FileWriter(path.append("catalina.properties").toFile()));
+				bw.write(propertiesFile);
+				bw.close();
+			}
+			monitor.worked(1);
+			
+			if (monitor.isCanceled())
+				return;
+			monitor.done();
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Could not save Tomcat v10.1 configuration to " + path, e);
+			throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorCouldNotSaveConfiguration, new String[] {e.getLocalizedMessage()}), e));
+		}
+	}
+
+	/**
+	 * Save to the given directory.  All configuration files
+	 * are forced to be saved.
+	 * 
+	 * @param path Desination path for the configuration files.
+	 * @param monitor A progress monitor
+	 * @exception CoreException
+	 */
+	public void save(IPath path, IProgressMonitor monitor) throws CoreException {
+		save(path, true, monitor);
+	}
+
+	/**
+	 * Save the information held by this object to the given directory.
+	 *
+	 * @param folder a folder
+	 * @param monitor a progress monitor
+	 * @throws CoreException
+	 */
+	public void save(IFolder folder, IProgressMonitor monitor) throws CoreException {
+		try {
+			monitor = ProgressUtil.getMonitorFor(monitor);
+			monitor.beginTask(Messages.savingTask, 1200);
+	
+			// save server.xml
+			byte[] data = serverFactory.getContents();
+			InputStream in = new ByteArrayInputStream(data);
+			IFile file = folder.getFile("server.xml");
+			if (file.exists()) {
+				if (isServerDirty)
+					file.setContents(in, true, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+				else
+					monitor.worked(200);
+			} else
+				file.create(in, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			isServerDirty = false;
+			
+			// save web.xml
+			webAppDocument.save(folder.getFile("web.xml"), ProgressUtil.getSubMonitorFor(monitor, 200));
+			
+			// save context.xml
+			if (contextDocument != null) {
+				data = XMLUtil.getContents(contextDocument);
+				in = new ByteArrayInputStream(data);
+				file = folder.getFile("context.xml");
+				if (file.exists())
+					monitor.worked(200);
+					//file.setContents(in, true, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+				else
+					file.create(in, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			}
+			
+			// save tomcat-users.xml
+			data = XMLUtil.getContents(tomcatUsersDocument);
+			in = new ByteArrayInputStream(data);
+			file = folder.getFile("tomcat-users.xml");
+			if (file.exists())
+				monitor.worked(200);
+				//file.setContents(in, true, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			else
+				file.create(in, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			
+			// save catalina.policy
+			in = new ByteArrayInputStream(policyFile.getBytes());
+			file = folder.getFile("catalina.policy");
+			if (file.exists())
+				monitor.worked(200);
+				//file.setContents(in, true, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			else
+				file.create(in, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			
+			// save catalina.properties
+			if (propertiesFile != null) {
+				in = new ByteArrayInputStream(propertiesFile.getBytes());
+				file = folder.getFile("catalina.properties");
+				if (file.exists())
+					monitor.worked(200);
+					//file.setContents(in, true, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+				else
+					file.create(in, true, ProgressUtil.getSubMonitorFor(monitor, 200));
+			} else
+				monitor.worked(200);
+			
+			if (monitor.isCanceled())
+				return;
+			monitor.done();
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Could not save Tomcat v10.1 configuration to " + folder.toString(), e);
+			throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorCouldNotSaveConfiguration, new String[] {e.getLocalizedMessage()}), e));
+		}
+	}
+
+	protected static boolean hasMDBListener(Server server) {
+		if (server == null)
+			return false;
+		
+		int count = server.getListenerCount();
+		if (count == 0)
+			return false;
+			
+		for (int i = 0; i < count; i++) {
+			Listener listener = server.getListener(i);
+			if (listener != null && listener.getClassName() != null && listener.getClassName().indexOf("mbean") >= 0)
+				return true;
+		}
+		return false;
+	}
+	
+	/**
+	 * @see ITomcatConfigurationWorkingCopy#addMimeMapping(int, IMimeMapping)
+	 */
+	public void addMimeMapping(int index, IMimeMapping map) {
+		webAppDocument.addMimeMapping(index, map);
+		firePropertyChangeEvent(ADD_MAPPING_PROPERTY, new Integer(index), map);
+	}
+
+	/**
+	 * @see ITomcatConfigurationWorkingCopy#addWebModule(int, ITomcatWebModule)
+	 */
+	public void addWebModule(int index, ITomcatWebModule module) {
+		try {
+			Context context = serverInstance.createContext(index);
+			if (context != null) {
+				context.setDocBase(module.getDocumentBase());
+				context.setPath(module.getPath());
+				context.setReloadable(module.isReloadable() ? "true" : "false");
+				if (module.getMemento() != null && module.getMemento().length() > 0)
+					context.setSource(module.getMemento());
+				isServerDirty = true;
+				firePropertyChangeEvent(ADD_WEB_MODULE_PROPERTY, null, module);
+			}
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error adding web module " + module.getPath(), e);
+		}
+	}
+
+	/**
+	 * Change the extension of a mime mapping.
+	 * 
+	 * @param index
+	 * @param map
+	 */
+	public void modifyMimeMapping(int index, IMimeMapping map) {
+		webAppDocument.modifyMimeMapping(index, map);
+		firePropertyChangeEvent(MODIFY_MAPPING_PROPERTY, new Integer(index), map);
+	}
+
+	/**
+	 * Modify the port with the given id.
+	 *
+	 * @param id java.lang.String
+	 * @param port int
+	 */
+	public void modifyServerPort(String id, int port) {
+		try {
+			if ("server".equals(id)) {
+				server.setPort(port + "");
+				isServerDirty = true;
+				firePropertyChangeEvent(MODIFY_PORT_PROPERTY, id, new Integer(port));
+				return;
+			}
+	
+			int i = id.indexOf("/");
+			// If a connector in the instance Service
+			if (i < 0) {
+				int connNum = Integer.parseInt(id);
+				Connector connector = serverInstance.getConnector(connNum);
+				if (connector != null) {
+					connector.setPort(port + "");
+					isServerDirty = true;
+					firePropertyChangeEvent(MODIFY_PORT_PROPERTY, id, new Integer(port));
+				}
+			}
+			// Else a connector in another Service
+			else {
+				int servNum = Integer.parseInt(id.substring(0, i));
+				int connNum = Integer.parseInt(id.substring(i + 1));
+				
+				Service service = server.getService(servNum);
+				Connector connector = service.getConnector(connNum);
+				connector.setPort(port + "");
+				isServerDirty = true;
+				firePropertyChangeEvent(MODIFY_PORT_PROPERTY, id, new Integer(port));
+			}
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error modifying server port " + id, e);
+		}
+	}
+	/**
+	 * Change a web module.
+	 * @param index int
+	 * @param docBase java.lang.String
+	 * @param path java.lang.String
+	 * @param reloadable boolean
+	 */
+	public void modifyWebModule(int index, String docBase, String path, boolean reloadable) {
+		try {
+			Context context = serverInstance.getContext(index);
+			if (context != null) {
+				context.setPath(path);
+				context.setDocBase(docBase);
+				context.setReloadable(reloadable ? "true" : "false");
+				isServerDirty = true;
+				WebModule module = new WebModule(path, docBase, null, reloadable);
+				firePropertyChangeEvent(MODIFY_WEB_MODULE_PROPERTY, new Integer(index), module);
+			}
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error modifying web module " + index, e);
+		}
+	}
+
+	/**
+	 * Removes a mime mapping.
+	 * @param index int
+	 */
+	public void removeMimeMapping(int index) {
+		webAppDocument.removeMimeMapping(index);
+		firePropertyChangeEvent(REMOVE_MAPPING_PROPERTY, null, new Integer(index));
+	}
+
+	/**
+	 * Removes a web module.
+	 * @param index int
+	 */
+	public void removeWebModule(int index) {
+		try {
+			serverInstance.removeContext(index);
+			isServerDirty = true;
+			firePropertyChangeEvent(REMOVE_WEB_MODULE_PROPERTY, null, new Integer(index));
+		} catch (Exception e) {
+			Trace.trace(Trace.SEVERE, "Error removing module ref " + index, e);
+		}
+	}
+
+	/**
+	 * Add context configuration found in META-INF/context.xml files
+	 * present in projects to published server.xml.
+	 * 
+	 * @param baseDir path to catalina instance directory
+	 * @param deployDir path to deployment directory
+	 * @param monitor a progress monitor or null
+	 * @return result of operation
+	 */
+	protected IStatus publishContextConfig(IPath baseDir, IPath deployDir, IProgressMonitor monitor) {
+		return TomcatVersionHelper.publishCatalinaContextConfig(baseDir, deployDir, monitor);
+	}
+	
+	/**
+	 * Update contexts in server.xml to serve projects directly without
+	 * publishing.
+	 * 
+	 * @param baseDir path to catalina instance directory
+	 * @param monitor a progress monitor or null
+	 * @return result of operation
+	 */
+	protected IStatus updateContextsToServeDirectly(IPath baseDir, String tomcatVersion, String loader, IProgressMonitor monitor) {
+		return TomcatVersionHelper.updateContextsToServeDirectly(baseDir, tomcatVersion, loader, true, monitor);
+	}
+
+	/**
+	 * Cleanup the server instance.  This consists of deleting the work
+	 * directory associated with Contexts that are going away in the
+	 * up coming publish.
+	 * 
+	 * @param baseDir path to server instance directory, i.e. catalina.base
+	 * @param installDir path to server installation directory (not currently used)
+	 * @param monitor a progress monitor or null
+	 * @return MultiStatus containing results of the cleanup operation
+	 */
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
+		List modules = getWebModules();
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
+	}
+
+	/**
+	 * @see TomcatConfiguration#localizeConfiguration(IPath, IPath, TomcatServer, IProgressMonitor)
+	 */
+	public IStatus localizeConfiguration(IPath baseDir, IPath deployDir, TomcatServer tomcatServer, IProgressMonitor monitor) {
+		return TomcatVersionHelper.localizeConfiguration(baseDir, deployDir, tomcatServer, monitor);
+	}
+}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Handler.java
new file mode 100644
index 0000000..f4becff
--- /dev/null
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101Handler.java
@@ -0,0 +1,195 @@
+/**********************************************************************
+ * Copyright (c) 2020, 2022 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors:
+ *    IBM Corporation - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.jst.server.tomcat.core.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
+import org.eclipse.jdt.launching.JavaRuntime;
+import org.eclipse.wst.server.core.IModule;
+/**
+ * Tomcat 101 handler.
+ */
+public class Tomcat101Handler implements ITomcatVersionHandler {
+	/**
+	 * @see ITomcatVersionHandler#verifyInstallPath(IPath)
+	 */
+	public IStatus verifyInstallPath(IPath installPath) {
+		IStatus result = TomcatVersionHelper.checkCatalinaVersion(installPath, TomcatPlugin.TOMCAT_101);
+		// If check was canceled, use folder check
+		if (result.getSeverity() == IStatus.CANCEL) {
+			result = TomcatPlugin.verifyInstallPathWithFolderCheck(installPath, TomcatPlugin.TOMCAT_101);
+		}
+		return result;
+	}
+	
+	/**
+	 * @see ITomcatVersionHandler#getRuntimeClass()
+	 */
+	public String getRuntimeClass() {
+		return "org.apache.catalina.startup.Bootstrap";
+	}
+	
+	/**
+	 * @see ITomcatVersionHandler#getRuntimeClasspath(IPath, IPath)
+	 */
+	public List getRuntimeClasspath(IPath installPath, IPath configPath) {
+		List<IRuntimeClasspathEntry> cp = new ArrayList<IRuntimeClasspathEntry>();
+		
+		// 10.1 - add bootstrap.jar and tomcat-juli.jar from the Tomcat bin directory
+		IPath binPath = installPath.append("bin");
+		if (binPath.toFile().exists()) {
+			IPath path = binPath.append("bootstrap.jar");
+			cp.add(JavaRuntime.newArchiveRuntimeClasspathEntry(path));
+			// Add commons-daemon.jar if it exists
+			path = binPath.append("commons-daemon.jar");
+			if (path.toFile().exists()) {
+				cp.add(JavaRuntime.newArchiveRuntimeClasspathEntry(path));
+			}
+			// Add tomcat-juli.jar if it exists
+			path = binPath.append("tomcat-juli.jar");
+			if (path.toFile().exists()) {
+				cp.add(JavaRuntime.newArchiveRuntimeClasspathEntry(path));
+			}
+			// If tomcat-juli.jar is not found in the install, check the config directory
+			else if (configPath != null){
+				path = configPath.append("bin/tomcat-juli.jar");
+				if (path.toFile().exists()) {
+					cp.add(JavaRuntime.newArchiveRuntimeClasspathEntry(path));
+				}
+			}
+		}
+		
+		return cp;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getRuntimeProgramArguments(IPath, boolean, boolean)
+	 */
+	public String[] getRuntimeProgramArguments(IPath configPath, boolean debug, boolean starting) {
+		List<String> list = new ArrayList<String>();
+
+		if (starting)
+			list.add("start");
+		else
+			list.add("stop");
+		
+		String[] temp = new String[list.size()];
+		list.toArray(temp);
+		return temp;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getExcludedRuntimeProgramArguments(boolean, boolean)
+	 */
+	public String[] getExcludedRuntimeProgramArguments(boolean debug, boolean starting) {
+		return null;
+	}
+	
+	/**
+	 * @see ITomcatVersionHandler#getRuntimeVMArguments(IPath, IPath, IPath, boolean)
+	 */
+	public String[] getRuntimeVMArguments(IPath installPath, IPath configPath, IPath deployPath, boolean isTestEnv) {
+		return TomcatVersionHelper.getCatalinaVMArguments(installPath, configPath, deployPath, isTestEnv);
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getRuntimePolicyFile(IPath)
+	 */
+	public String getRuntimePolicyFile(IPath configPath) {
+		return configPath.append("conf").append("catalina.policy").toOSString();
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#canAddModule(IModule)
+	 */
+	public IStatus canAddModule(IModule module) {
+		String version = module.getModuleType().getVersion();
+		if ("2.2".equals(version) || "2.3".equals(version) || "2.4".equals(version) || "2.5".equals(version)
+				|| "3.0".equals(version) || "3.1".equals(version) || "4.0".equals(version) || "5.0".equals(version) || "6.0".equals(version))
+			return Status.OK_STATUS;
+		
+		return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, Messages.errorSpec101, null);
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getRuntimeBaseDirectory(TomcatServer)
+	 */
+	public IPath getRuntimeBaseDirectory(TomcatServer server) {
+		return TomcatVersionHelper.getStandardBaseDirectory(server);
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#prepareRuntimeDirectory(IPath)
+	 */
+	public IStatus prepareRuntimeDirectory(IPath baseDir) {
+		return TomcatVersionHelper.createCatalinaInstanceDirectory(baseDir);
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#prepareDeployDirectory(IPath)
+	 */
+	public IStatus prepareDeployDirectory(IPath deployPath) {
+		return TomcatVersionHelper.createDeploymentDirectory(deployPath,
+				TomcatVersionHelper.DEFAULT_WEBXML_SERVLET25);
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#prepareForServingDirectly(IPath, TomcatServer)
+	 */
+	public IStatus prepareForServingDirectly(IPath baseDir, TomcatServer server, String tomcatVersion) {
+		// Nothing beyond configuration required for Tomcat 10
+		return Status.OK_STATUS;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getSharedLoader(IPath)
+	 */
+	public String getSharedLoader(IPath baseDir) {
+		return "common";
+	}
+	
+	/**
+	 * Returns true since Tomcat 10.x supports this feature.
+	 * 
+	 * @return true since feature is supported
+	 */
+	public boolean supportsServeModulesWithoutPublish() {
+		return true;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsDebugArgument()
+	 */
+	public boolean supportsDebugArgument() {
+		return false;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return true;
+	}
+
+	/**
+	 * @see ITomcatVersionHandler#getEndorsedDirectories(IPath)
+	 */
+	public String getEndorsedDirectories(IPath installPath) {
+		return installPath.append("endorsed").toOSString();
+	}	
+}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101PublishModuleVisitor.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101PublishModuleVisitor.java
new file mode 100644
index 0000000..50de442
--- /dev/null
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat101PublishModuleVisitor.java
@@ -0,0 +1,271 @@
+/**********************************************************************
+ * Copyright (c) 2020, 2022 SAS Institute and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ * 
+ * Contributors:
+ *    SAS Institute - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.jst.server.tomcat.core.internal;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Context;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.PostResources;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.PreResources;
+import org.eclipse.jst.server.tomcat.core.internal.xml.server40.ServerInstance;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
+import org.eclipse.wst.server.core.IModule;
+import org.eclipse.wst.server.core.ServerUtil;
+
+public class Tomcat101PublishModuleVisitor extends TomcatPublishModuleVisitor {
+
+	/**
+	 * Instantiate a new Tomcat101PublishModuleVisitor
+	 * 
+	 * @param baseDir catalina base path
+	 * @param tomcatVersion tomcat version
+	 * @param serverInstance ServerInstance containing server.xml contents
+	 * @param sharedLoader string value for shared.loader catalina configuration property
+	 * @param enableMetaInfResources flag to indicate if Servlet 3.0 or greater "META-INF/resources" feature should be supported
+	 */
+	Tomcat101PublishModuleVisitor(IPath baseDir, String tomcatVersion, ServerInstance serverInstance, String sharedLoader, boolean enableMetaInfResources) {
+		super(baseDir, tomcatVersion, serverInstance, sharedLoader, enableMetaInfResources);
+	}
+
+    /**
+     * {@inheritDoc}
+     */
+	@Override
+    public void endVisitWebComponent(IVirtualComponent component)
+            throws CoreException {
+
+        // track context changes, don't rewrite if not needed
+        boolean dirty = false;
+
+        IModule module = ServerUtil.getModule(component.getProject());
+
+        // we need this for the user-specified context path
+        Context context = findContext(module);
+        if (context == null) {
+        	String name = module != null ? module.getName() : component.getName();
+    		Trace.trace(Trace.SEVERE, "Could not find context for module " + name);
+    		throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
+    				NLS.bind(Messages.errorPublishContextNotFound, name), null));
+        }
+
+		dirty = includeProjectContextXml(component, context);
+		dirty = updateDocBaseAndPath(component, context);
+
+		// Add WEB-INF/classes elements as PreResources
+		for (Iterator iterator = virtualClassClasspathElements.iterator();
+				iterator.hasNext();) {
+			Object virtualClassClasspathElement = iterator.next();
+			PreResources preResources = (PreResources)context.getResources().createElement("PreResources");
+			preResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+			preResources.setBase(virtualClassClasspathElement.toString());
+			preResources.setWebAppMount("/WEB-INF/classes");
+			preResources.setInternalPath("/");
+			preResources.setClassLoaderOnly("false");
+		}
+		virtualClassClasspathElements.clear();
+
+		// Add Jars as PreResources if a jar, or as PostResources if a utility project
+		for (Iterator iterator = virtualJarClasspathElements.iterator();
+				iterator.hasNext();) {
+			Object virtualJarClassClasspathElement = iterator.next();
+			String jarPath = virtualJarClassClasspathElement.toString();
+			if (jarPath.endsWith(".jar")) {
+				PreResources preResources = (PreResources)context.getResources().createElement("PreResources");
+				preResources.setClassName("org.apache.catalina.webresources.FileResourceSet");
+				preResources.setBase(jarPath);
+				preResources.setWebAppMount("/WEB-INF/lib/" + new File(jarPath).getName());
+				preResources.setInternalPath("/");
+				preResources.setClassLoaderOnly("false");
+			}
+			else {
+				PostResources postResources = (PostResources)context.getResources().createElement("PostResources");
+				postResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+				postResources.setBase(jarPath);
+				postResources.setWebAppMount("/WEB-INF/classes");
+				postResources.setInternalPath("/");
+				postResources.setClassLoaderOnly("false");
+				// Map META-INF tld files to WEB-INF
+				File metaInfDir = new File(jarPath + "/META-INF");
+				if (metaInfDir.isDirectory() && metaInfDir.exists()) {
+					// Map META-INF directory directly to /META-INF
+					postResources = (PostResources)context.getResources().createElement("PostResources");
+					postResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+					postResources.setBase(metaInfDir.getPath());
+					postResources.setWebAppMount("/META-INF");
+					postResources.setInternalPath("/");
+					postResources.setClassLoaderOnly("false");
+
+					File [] tldFiles = metaInfDir.listFiles(new FileFilter() {
+							public boolean accept(File file) {
+								if (file.isFile() && file.getName().endsWith(".tld")) {
+									return true;
+								}
+								return false;
+							}
+						});
+					for (int i = 0; i < tldFiles.length; i++) {
+						postResources = (PostResources)context.getResources().createElement("PostResources");
+						postResources.setClassName("org.apache.catalina.webresources.FileResourceSet");
+						postResources.setBase(tldFiles[0].getPath());
+						postResources.setWebAppMount("/WEB-INF/" + tldFiles[0].getName());
+						postResources.setInternalPath("/");
+						postResources.setClassLoaderOnly("false");
+					}
+				}
+			}
+		}
+		virtualJarClasspathElements.clear();
+
+		Set<String> rtPathsProcessed = new HashSet<String>();
+		Set<String> locationsIncluded = new HashSet<String>();
+		String docBase = context.getDocBase();
+		locationsIncluded.add(docBase);
+		Map<String, String> retryLocations = new HashMap<String, String>();
+		IVirtualResource [] virtualResources = component.getRootFolder().getResources("");
+		// Loop over the module's resources
+		for (int i = 0; i < virtualResources.length; i++) {
+			String rtPath = virtualResources[i].getRuntimePath().toString();
+			// Note: The virtual resources returned only know their runtime path.
+			// Asking for the project path for this resource performs a lookup
+			// that will only return the path for the first mapping for the
+			// runtime path.  Thus use of getUnderlyingResources() is necessary.
+			// However, this returns matching resources from all mappings so
+			// we have to try to keep only those that are mapped directly
+			// to the runtime path in the .components file.
+
+			// If this runtime path has not yet been processed
+			if (!rtPathsProcessed.contains(rtPath)) {
+				// If not a Java related resource
+				if (!"/WEB-INF/classes".equals(rtPath)) {
+					// Get all resources for this runtime path
+					IResource[] underlyingResources = virtualResources[i].getUnderlyingResources();
+					// If resource is mapped to "/", then we know it corresponds directly
+					// to a mapping in the .components file
+					if ("/".equals(rtPath)) {
+						for (int j = 0; j < underlyingResources.length; j++) {
+							IPath resLoc = underlyingResources[j].getLocation();
+							String location = resLoc.toOSString();
+							if (!location.equals(docBase)) {
+								PreResources preResources = (PreResources)context.getResources().createElement("PreResources");
+								preResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+								preResources.setBase(location);
+								preResources.setWebAppMount("/");
+								preResources.setInternalPath("/");
+								preResources.setClassLoaderOnly("false");
+								// Add to the set of locations included
+								locationsIncluded.add(location);
+							}
+						}
+					}
+					// Else this runtime path is something other than "/"
+					else {
+						int idx = rtPath.lastIndexOf('/');
+						// If a "normal" runtime path
+						if (idx >= 0) {
+							// Get the name of the last segment in the runtime path
+							String lastSegment = rtPath.substring(idx + 1);
+							// Check the underlying resources to determine which correspond to mappings
+							for (int j = 0; j < underlyingResources.length; j++) {
+								IPath resLoc = underlyingResources[j].getLocation();
+								String location = resLoc.toOSString();
+								// If the last segment of the runtime path doesn't match the
+								// the last segment of the location, then we have a direct mapping
+								// from the .contents file.
+								if (!lastSegment.equals(resLoc.lastSegment())) {
+									PreResources preResources = (PreResources)context.getResources().createElement("PreResources");
+									preResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+									preResources.setBase(location);
+									preResources.setWebAppMount(rtPath);
+									preResources.setInternalPath("/");
+									preResources.setClassLoaderOnly("false");
+									// Add to the set of locations included
+									locationsIncluded.add(location);
+								}
+								// Else last segment of runtime path did match the last segment
+								// of the location.  We likely have a subfolder of a mapping
+								// that matches a portion of the runtime path.
+								else {
+									// Since we can't be sure, save so it can be check again later
+									retryLocations.put(location, rtPath);
+								}
+							}
+						}
+					}
+				}
+				// Add the runtime path to those already processed
+				rtPathsProcessed.add(rtPath);
+			}
+		}
+		// If there are locations to retry, add any not yet included in extra paths setting
+		if (!retryLocations.isEmpty()) {
+			// Remove retry locations already included in the extra paths
+			for (Iterator iterator = retryLocations.keySet().iterator(); iterator.hasNext();) {
+				String location = (String)iterator.next();
+				for (Iterator iterator2 = locationsIncluded.iterator(); iterator2.hasNext();) {
+					String includedLocation = (String)iterator2.next();
+					if (location.equals(includedLocation) || location.startsWith(includedLocation + File.separator)) {
+						iterator.remove();
+						break;
+					}
+				}
+			}
+			// If any entries are left, include them in the extra paths
+			if (!retryLocations.isEmpty()) {
+				for (Iterator iterator = retryLocations.entrySet().iterator(); iterator.hasNext();) {
+					Map.Entry entry = (Map.Entry)iterator.next();
+					String location = (String)entry.getKey();
+					String rtPath = (String)entry.getValue();
+					PreResources preResources = (PreResources)context.getResources().createElement("PreResources");
+					preResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+					preResources.setBase(location);
+					preResources.setWebAppMount(rtPath);
+					preResources.setInternalPath("/");
+					preResources.setClassLoaderOnly("false");
+				}
+			}
+		}
+		if (!virtualDependentResources.isEmpty()) {
+			for (Map.Entry<String, List<String>> entry : virtualDependentResources.entrySet()) {
+				String rtPath = entry.getKey();
+				List<String> locations = entry.getValue();
+				for (String location : locations) {
+					PostResources postResources = (PostResources)context.getResources().createElement("PostResources");
+					postResources.setClassName("org.apache.catalina.webresources.DirResourceSet");
+					postResources.setBase(location);
+					postResources.setWebAppMount(rtPath.length() > 0 ? rtPath : "/");
+					postResources.setInternalPath("/");
+					postResources.setClassLoaderOnly("false");
+				}
+			}
+		}
+		virtualDependentResources.clear();
+
+		if (dirty) {
+			//TODO If writing to separate context XML files, save "dirty" status for later use
+		}
+	}
+}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatPlugin.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatPlugin.java
index 4a81504..39e5479 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatPlugin.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatPlugin.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2020 IBM Corporation and others.
+ * Copyright (c) 2003, 2022 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -47,6 +47,7 @@
 	public static final String TOMCAT_85 = "org.eclipse.jst.server.tomcat.85";
 	public static final String TOMCAT_90 = "org.eclipse.jst.server.tomcat.90";
 	public static final String TOMCAT_100 = "org.eclipse.jst.server.tomcat.100";
+	public static final String TOMCAT_101 = "org.eclipse.jst.server.tomcat.101";
 
 	// Beyond 8.0, this verification approach is not effective and actually isn't currently used for 7.0 and beyond (see verifyInstallPath method).
 	protected static final String VERIFY_INSTALL_FILE = "verifyInstall.properties";
@@ -167,6 +168,8 @@
 			return new Tomcat90Handler();
 		else if (TOMCAT_100.equals(id))
 			return new Tomcat100Handler();
+		else if (TOMCAT_101.equals(id))
+			return new Tomcat101Handler();
 		else
 			return null;
 	}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntime.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntime.java
index fe507cc..1d493bb 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntime.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntime.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2020 IBM Corporation and others.
+ * Copyright (c) 2003, 2022 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -142,7 +142,7 @@
 		String id = getRuntime().getRuntimeType().getId();
 		if (!found) {
 			if (id != null && (id.indexOf("55") > 0 || id.indexOf("60") > 0 || id.indexOf("70") > 0 || id.indexOf("80") > 0
-					|| id.indexOf("85") > 0 || id.indexOf("90") > 0 || id.indexOf("100") > 0)) {
+					|| id.indexOf("85") > 0 || id.indexOf("90") > 0 || id.indexOf("100") > 0 || id.indexOf("101") > 0)) {
 				found = true;
 			}
 		}
@@ -232,6 +232,17 @@
 			}
 		}
 
+		// Else for Tomcat 10.1, ensure we have J2SE 8.0
+		else if (id != null && id.indexOf("101") > 0) {
+			IVMInstall vmInstall = getVMInstall();
+			if (vmInstall instanceof IVMInstall2) {
+				String javaVersion = ((IVMInstall2)vmInstall).getJavaVersion();
+				if (javaVersion != null && !isVMMinimumVersion(javaVersion, 1100)) {
+					return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, Messages.errorJRETomcat101, null);
+				}
+			}
+		}
+
 		return Status.OK_STATUS;
 	}
 
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeClasspathProvider.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeClasspathProvider.java
index 91c4661..45fa7bd 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeClasspathProvider.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeClasspathProvider.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2021 IBM Corporation and others.
+ * Copyright (c) 2003, 2022 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -53,17 +53,17 @@
 			case 8 :
 				url = "https://javaee.github.io/javaee-spec/javadocs/";
 				break;
-			default :
-				// Jakarta EE uses a different URL for each component specification
+			case 9 :
+				// Jakarta EE 9 uses a different URL for each component specification
 				url = "https://jakarta.ee/specifications/servlet/5.0/apidocs/";
 				if (jarName.contains("jsp")) {
 					url = "https://jakarta.ee/specifications/pages/3.0/apidocs/";
 				}
 				else if (jarName.contains("websocket")) {
-					url = "https://jakarta.ee/specifications/websocket/2.0/apidocs/";
+					url = "https://jakarta.ee/specifications/websocket/2.0/apidocs/"; // URL doesn't currently work
 				}
 				else if (jarName.contains("annotation")) {
-					url = "https://jakarta.ee/specifications/annotations/2.0/apidocs/jakarta.annotation/";
+					url = "https://jakarta.ee/specifications/annotations/2.0/apidocs/jakarta.annotation/"; // URL doesn't currently work
 				}
 				else if (jarName.equals("el-api.jar")) {
 					url = "https://jakarta.ee/specifications/expression-language/4.0/apidocs/";
@@ -71,19 +71,41 @@
 				else if (jarName.contains("jaspic")) {
 					url = "https://jakarta.ee/specifications/authentication/2.0/apidocs/";
 				}
+				break;
+			default :
+				// Jakarta EE 10 uses a different URL for each component specification
+				url = "https://jakarta.ee/specifications/servlet/6.0/apidocs/";
+				if (jarName.contains("jsp")) {
+					url = "https://jakarta.ee/specifications/pages/3.1/apidocs/";
+				}
+				else if (jarName.contains("websocket")) {
+					url = "https://jakarta.ee/specifications/websocket/2.1/apidocs/"; // URL doesn't currently work
+				}
+				else if (jarName.contains("annotation")) {
+					url = "https://jakarta.ee/specifications/annotations/2.1/apidocs/jakarta.annotation/"; // URL doesn't currently work
+				}
+				else if (jarName.equals("el-api.jar")) {
+					url = "https://jakarta.ee/specifications/expression-language/5.0/apidocs/";
+				}
+				else if (jarName.contains("jaspic")) {
+					url = "https://jakarta.ee/specifications/authentication/3.0/apidocs/";
+				}
 		}
 
 		return url;
 	}
 
 	private String getTomcatJavadocLocation(IRuntime runtime) {
-		/* Default to v10.0 doc. v7.0 is currently the oldest advertised version on the front page */
-		String tomcatDocURL = "http://tomcat.apache.org/tomcat-10.0-doc/api/";
+		/* Default to v10.1 doc. v7.0 is currently the oldest advertised version on the front page */
+		String tomcatDocURL = "http://tomcat.apache.org/tomcat-10.1-doc/api/";
 		String runtimeTypeId = runtime.getRuntimeType().getId();
-		if (runtimeTypeId.indexOf("90") > 0) {
+		if (runtimeTypeId.indexOf("100") > 0) {
+			tomcatDocURL = "http://tomcat.apache.org/tomcat-10.0-doc/api/";
+		}
+		else if (runtimeTypeId.indexOf("90") > 0) {
 			tomcatDocURL = "https://tomcat.apache.org/tomcat-9.0-doc/api/";
 		}
-		if (runtimeTypeId.indexOf("85") > 0) {
+		else if (runtimeTypeId.indexOf("85") > 0) {
 			tomcatDocURL = "https://tomcat.apache.org/tomcat-8.5-doc/api/";
 		}
 		else if (runtimeTypeId.indexOf("80") > 0) {
@@ -111,7 +133,7 @@
 		if (runtimeId.indexOf("32") > 0) {
 			IPath path = installPath.append("lib");
 			addLibraryEntries(list, path.toFile(), true);
-		} else if (runtimeId.indexOf("60") > 0 || runtimeId.indexOf("70") > 0 || runtimeId.indexOf("80") > 0 || runtimeId.indexOf("85") > 0 || runtimeId.indexOf("90") > 0 || runtimeId.indexOf("100") > 0) {
+		} else if (runtimeId.indexOf("60") > 0 || runtimeId.indexOf("70") > 0 || runtimeId.indexOf("80") > 0 || runtimeId.indexOf("85") > 0 || runtimeId.indexOf("90") > 0 || runtimeId.indexOf("100") > 0 || runtimeId.indexOf("101") > 0) {
 			// TODO May need some flexibility in case the installation has been configured differently
 			// This lib "simplification" may cause issues for some.
 			// Not known yet whether packaged Linux installs will go along.
@@ -133,7 +155,10 @@
 		 * entire project. -1 represents unknown.
 		 */
 		int eeVersion = -1;
-		if (runtimeId.indexOf("100") > 0) {
+		if (runtimeId.indexOf("101") > 0) {
+			eeVersion = 10;
+		}
+		else if (runtimeId.indexOf("100") > 0) {
 			eeVersion = 9;
 		}
 		else if (runtimeId.indexOf("90") > 0) {
@@ -155,7 +180,10 @@
 					IProjectFacet webModuleFacet = ProjectFacetsManager.getProjectFacet(JST_WEB_FACET_ID);
 					if (faceted.hasProjectFacet(webModuleFacet)) {
 						String servletVersionStr = faceted.getInstalledVersion(webModuleFacet).getVersionString();
-						if (servletVersionStr.equals("5.0")) {
+						if (servletVersionStr.equals("6.0")) {
+							eeVersion = 10;
+						}
+						else if (servletVersionStr.equals("5.0")) {
 							eeVersion = 9;
 						}
 						else if (servletVersionStr.equals("4.0")) {
@@ -181,11 +209,11 @@
 			}
 			catch (NumberFormatException e) {
 				// default to the latest
-				eeVersion = 9;
+				eeVersion = 10;
 			}
 			catch (CoreException e) {
 				// default to the latest
-				eeVersion = 9;
+				eeVersion = 10;
 			}
 		}
 
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeLocator.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeLocator.java
index 8c8883d..79e9acb 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeLocator.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatRuntimeLocator.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2020 IBM Corporation and others.
+ * Copyright (c) 2003, 2022 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -37,7 +37,8 @@
 		"org.eclipse.jst.server.tomcat.runtime.80",
 		"org.eclipse.jst.server.tomcat.runtime.85",
 		"org.eclipse.jst.server.tomcat.runtime.90",
-		"org.eclipse.jst.server.tomcat.runtime.100"};
+		"org.eclipse.jst.server.tomcat.runtime.100",
+		"org.eclipse.jst.server.tomcat.runtime.101"};
 
 	/* (non-Javadoc)
 	 * @see org.eclipse.wst.server.core.model.IRuntimeFactoryDelegate#getKnownRuntimes()
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
index 657ea2c..3c98bbe 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
@@ -126,6 +126,8 @@
 					tcConfig = new Tomcat90Configuration(folder);
 				else if (id.indexOf("100") > 0)
 					tcConfig = new Tomcat100Configuration(folder);
+				else if (id.indexOf("101") > 0)
+					tcConfig = new Tomcat101Configuration(folder);
 				else {
 					throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, Messages.errorUnknownVersion, null));
 				}
@@ -185,6 +187,8 @@
 			tcConfig = new Tomcat90Configuration(folder);
 		else if (id.indexOf("100") > 0)
 			tcConfig = new Tomcat100Configuration(folder);
+		else if (id.indexOf("101") > 0)
+			tcConfig = new Tomcat101Configuration(folder);
 		else {
 			throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, Messages.errorUnknownVersion, null));
 		}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
index d9dae2b..fdf16dc 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
@@ -1,5 +1,5 @@
 /**********************************************************************
- * Copyright (c) 2007, 2020 SAS Institute, Inc and others.
+ * Copyright (c) 2007, 2022 SAS Institute, Inc and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -112,6 +112,7 @@
 		versionStringMap.put(TomcatPlugin.TOMCAT_85, "8.5.");
 		versionStringMap.put(TomcatPlugin.TOMCAT_90, "9.0.");
 		versionStringMap.put(TomcatPlugin.TOMCAT_100, "10.0.");
+		versionStringMap.put(TomcatPlugin.TOMCAT_101, "10.1.");
 	}
 
 	/**
@@ -924,7 +925,8 @@
 			boolean isTomcat80 = tomcatVersion.startsWith("8.0");
 			boolean isTomcat85 = tomcatVersion.startsWith("8.5");
 			boolean isTomcat9 = tomcatVersion.startsWith("9.");
-			boolean isTomcat10 = tomcatVersion.startsWith("10.");
+			boolean isTomcat100 = tomcatVersion.startsWith("10.0");
+			boolean isTomcat101 = tomcatVersion.startsWith("10.1");
 			// care about top-level modules only
 			TomcatPublishModuleVisitor visitor;
 			if (isTomcat80) {
@@ -939,10 +941,14 @@
 				visitor = new Tomcat90PublishModuleVisitor(
 						baseDir, tomcatVersion, publishedInstance, loader, enableMetaInfResources);
 			}
-			else if (isTomcat10) {
+			else if (isTomcat100) {
 				visitor = new Tomcat100PublishModuleVisitor(
 						baseDir, tomcatVersion, publishedInstance, loader, enableMetaInfResources);
 			}
+			else if (isTomcat101) {
+				visitor = new Tomcat101PublishModuleVisitor(
+						baseDir, tomcatVersion, publishedInstance, loader, enableMetaInfResources);
+			}
 			else {
 				visitor = new TomcatPublishModuleVisitor(
 						baseDir, tomcatVersion, publishedInstance, loader, enableMetaInfResources);
@@ -1156,7 +1162,7 @@
 		File jarFile = null;
 		
 		if (TomcatPlugin.TOMCAT_60.equals(serverType) || TomcatPlugin.TOMCAT_70.equals(serverType) || TomcatPlugin.TOMCAT_80.equals(serverType)
-				|| TomcatPlugin.TOMCAT_85.equals(serverType) || TomcatPlugin.TOMCAT_90.equals(serverType) || TomcatPlugin.TOMCAT_100.equals(serverType)) {
+				|| TomcatPlugin.TOMCAT_85.equals(serverType) || TomcatPlugin.TOMCAT_90.equals(serverType) || TomcatPlugin.TOMCAT_100.equals(serverType) || TomcatPlugin.TOMCAT_101.equals(serverType)) {
 			catalinaJarPath = installPath.append("lib").append("catalina.jar");
 			jarFile = catalinaJarPath.toFile();
 			// If jar is not at expected location, try alternate location
diff --git a/plugins/org.eclipse.jst.server.tomcat.ui/plugin.xml b/plugins/org.eclipse.jst.server.tomcat.ui/plugin.xml
index e18386b..5f91863 100644
--- a/plugins/org.eclipse.jst.server.tomcat.ui/plugin.xml
+++ b/plugins/org.eclipse.jst.server.tomcat.ui/plugin.xml
@@ -47,6 +47,10 @@
          id="org.eclipse.jst.server.tomcat.100"
          icon="icons/obj16/tomcat.gif"
          typeIds="org.eclipse.jst.server.tomcat.runtime.100"/>
+      <image
+         id="org.eclipse.jst.server.tomcat.101"
+         icon="icons/obj16/tomcat.gif"
+         typeIds="org.eclipse.jst.server.tomcat.runtime.101"/>
 
       <image
          id="org.eclipse.jst.server.tomcat.32"
@@ -92,6 +96,10 @@
          id="org.eclipse.jst.server.tomcat.100"
          icon="icons/obj16/tomcat.gif"
          typeIds="org.eclipse.jst.server.tomcat.100"/>
+      <image
+         id="org.eclipse.jst.server.tomcat.101"
+         icon="icons/obj16/tomcat.gif"
+         typeIds="org.eclipse.jst.server.tomcat.101"/>
    </extension>
    
    <extension point="org.eclipse.core.expressions.propertyTesters">
@@ -197,6 +205,10 @@
          id="org.eclipse.jst.server.tomcat.runtime.100"
          typeIds="org.eclipse.jst.server.tomcat.runtime.100"
          class="org.eclipse.jst.server.tomcat.ui.internal.TomcatRuntimeWizardFragment"/>
+      <fragment
+         id="org.eclipse.jst.server.tomcat.runtime.101"
+         typeIds="org.eclipse.jst.server.tomcat.runtime.101"
+         class="org.eclipse.jst.server.tomcat.ui.internal.TomcatRuntimeWizardFragment"/>
    </extension>
 
   <extension point="org.eclipse.debug.ui.launchConfigurationTypeImages">