LOG4J2-1606 LOG4J2-2624 Add flag to disable automatic log4j shutdown in log4j-web. (#463)
diff --git a/log4j-web/pom.xml b/log4j-web/pom.xml
index 3ce1f93..f321fa0 100644
--- a/log4j-web/pom.xml
+++ b/log4j-web/pom.xml
@@ -61,14 +61,18 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.junit.vintage</groupId>
-      <artifactId>junit-vintage-engine</artifactId>
+    	<groupId>org.hamcrest</groupId>
+    	<artifactId>hamcrest-all</artifactId>
     </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+	</dependency>
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-test</artifactId>
       <scope>test</scope>
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
index 19ac246..eda3853 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
@@ -57,7 +57,10 @@
             initializer.start();
             initializer.setLoggerContext(); // the application is just now starting to start up
 
-            servletContext.addListener(new Log4jServletContextListener());
+            if (!"true".equalsIgnoreCase(servletContext.getInitParameter(
+                    Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED))) {
+                servletContext.addListener(new Log4jServletContextListener());
+            }
 
             filter.setAsyncSupported(true); // supporting async when the user isn't using async has no downsides
             filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
index a121ccf..af756e3 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
@@ -24,7 +24,6 @@
 import javax.servlet.ServletContextListener;
 
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.LifeCycle;
 import org.apache.logging.log4j.core.LifeCycle2;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
@@ -51,6 +50,14 @@
     public void contextInitialized(final ServletContextEvent event) {
         this.servletContext = event.getServletContext();
         LOGGER.debug("Log4jServletContextListener ensuring that Log4j starts up properly.");
+        
+        if ("true".equalsIgnoreCase(servletContext.getInitParameter(
+                Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED))) {
+        	throw new IllegalStateException("Do not use " + getClass().getSimpleName() + " when "
+        			+ Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED + " is true. Please use "
+        			+ Log4jShutdownOnContextDestroyedListener.class.getSimpleName() + " instead of "
+        			+ getClass().getSimpleName() + ".");
+        }
 
         this.initializer = WebLoggerContextUtils.getWebLifeCycle(this.servletContext);
         try {
@@ -71,12 +78,16 @@
 
 		this.initializer.clearLoggerContext(); // the application is finished
 		// shutting down now
-        final String stopTimeoutStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT);
-        final long stopTimeout = Strings.isEmpty(stopTimeoutStr) ? DEFAULT_STOP_TIMEOUT
-                : Long.parseLong(stopTimeoutStr);
-        final String timeoutTimeUnitStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT_TIMEUNIT);
-        final TimeUnit timeoutTimeUnit = Strings.isEmpty(timeoutTimeUnitStr) ? DEFAULT_STOP_TIMEOUT_TIMEUNIT
-                : TimeUnit.valueOf(timeoutTimeUnitStr.toUpperCase(Locale.ROOT));
-        ((LifeCycle) this.initializer).stop(stopTimeout, timeoutTimeUnit);
+		if (initializer instanceof LifeCycle2) {
+			final String stopTimeoutStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT);
+			final long stopTimeout = Strings.isEmpty(stopTimeoutStr) ? DEFAULT_STOP_TIMEOUT
+					: Long.parseLong(stopTimeoutStr);
+			final String timeoutTimeUnitStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT_TIMEUNIT);
+			final TimeUnit timeoutTimeUnit = Strings.isEmpty(timeoutTimeUnitStr) ? DEFAULT_STOP_TIMEOUT_TIMEUNIT
+					: TimeUnit.valueOf(timeoutTimeUnitStr.toUpperCase(Locale.ROOT));
+			((LifeCycle2) this.initializer).stop(stopTimeout, timeoutTimeUnit);
+		} else {
+			this.initializer.stop();
+		}
 	}
 }
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListener.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListener.java
new file mode 100644
index 0000000..9bbe9c1
--- /dev/null
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListener.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.web;
+
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle2;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.Strings;
+
+public class Log4jShutdownOnContextDestroyedListener implements ServletContextListener {
+
+    private static final int DEFAULT_STOP_TIMEOUT = 30;
+    private static final TimeUnit DEFAULT_STOP_TIMEOUT_TIMEUNIT = TimeUnit.SECONDS;
+
+    private static final String KEY_STOP_TIMEOUT = "log4j.stop.timeout";
+    private static final String KEY_STOP_TIMEOUT_TIMEUNIT = "log4j.stop.timeout.timeunit";
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private ServletContext servletContext;
+    private Log4jWebLifeCycle initializer;
+
+    @Override
+    public void contextInitialized(final ServletContextEvent event) {
+        LOGGER.debug(Log4jShutdownOnContextDestroyedListener.class.getSimpleName() + 
+        		" ensuring that Log4j started up properly.");
+        servletContext = event.getServletContext();
+        if (null == servletContext.getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE)) {
+        	throw new IllegalStateException(
+        			"Context did not contain required Log4jWebLifeCycle in the " 
+        			+ Log4jWebSupport.SUPPORT_ATTRIBUTE + " attribute.");
+        }
+        this.initializer = WebLoggerContextUtils.getWebLifeCycle(servletContext);
+    }
+
+    @Override
+    public void contextDestroyed(final ServletContextEvent event) {
+        if (this.servletContext == null || this.initializer == null) {
+            LOGGER.warn("Context destroyed before it was initialized.");
+            return;
+        }
+        LOGGER.debug(Log4jShutdownOnContextDestroyedListener.class.getSimpleName() +
+        		" ensuring that Log4j shuts down properly.");
+
+        this.initializer.clearLoggerContext(); // the application is finished
+        // shutting down now
+        if (initializer instanceof LifeCycle2) {
+            final String stopTimeoutStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT);
+            final long stopTimeout = Strings.isEmpty(stopTimeoutStr) ? DEFAULT_STOP_TIMEOUT
+                    : Long.parseLong(stopTimeoutStr);
+            final String timeoutTimeUnitStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT_TIMEUNIT);
+            final TimeUnit timeoutTimeUnit = Strings.isEmpty(timeoutTimeUnitStr) ? DEFAULT_STOP_TIMEOUT_TIMEUNIT
+                    : TimeUnit.valueOf(timeoutTimeUnitStr.toUpperCase(Locale.ROOT));
+            ((LifeCycle2) this.initializer).stop(stopTimeout, timeoutTimeUnit);
+        } else {
+            this.initializer.stop();
+        }
+    }
+}
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebSupport.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebSupport.java
index c611a16..22ed45d 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebSupport.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebSupport.java
@@ -56,6 +56,13 @@
     String IS_LOG4J_AUTO_INITIALIZATION_DISABLED = "isLog4jAutoInitializationDisabled";
 
     /**
+     * The {@link javax.servlet.ServletContext} parameter name for the flag that disables Log4j's auto-shutdown
+     * in Servlet 3.0+ web applications. Set a context parameter with this name to "true" to disable
+     * auto-shutdown.
+     */
+    String IS_LOG4J_AUTO_SHUTDOWN_DISABLED = "isLog4jAutoShutdownDisabled";
+
+    /**
      * The attribute key for the {@link javax.servlet.ServletContext} attribute that the singleton support instance
      * is stored in.
      */
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContainerInitializerTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContainerInitializerTest.java
index 0cd3988..7b61f1c 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContainerInitializerTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContainerInitializerTest.java
@@ -24,22 +24,24 @@
 import javax.servlet.ServletContext;
 
 import org.apache.logging.log4j.util.Strings;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.never;
 import static org.mockito.BDDMockito.then;
 import static org.mockito.BDDMockito.willThrow;
 import static org.mockito.Mockito.mock;
 
-@RunWith(MockitoJUnitRunner.class)
+@ExtendWith(MockitoExtension.class)
 public class Log4jServletContainerInitializerTest {
     @Mock
     private ServletContext servletContext;
@@ -52,7 +54,7 @@
 
     private Log4jServletContainerInitializer containerInitializer;
 
-    @Before
+    @BeforeEach
     public void setUp() {
         this.containerInitializer = new Log4jServletContainerInitializer();
     }
@@ -83,6 +85,29 @@
     }
 
     @Test
+    public void testOnStartupWithServletVersion3_xEffectiveVersion3_xShutdownDisabled() throws Exception {
+        final FilterRegistration.Dynamic registration = mock(FilterRegistration.Dynamic.class);
+        given(servletContext.getMajorVersion()).willReturn(3);
+        given(servletContext.getEffectiveMajorVersion()).willReturn(3);
+        given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED)))
+                      .willReturn("true");
+        given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_AUTO_INITIALIZATION_DISABLED))).willReturn(
+                null);
+        given(servletContext.addFilter(eq("log4jServletFilter"), filterCaptor.capture())).willReturn(registration);
+        given(servletContext.getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE)).willReturn(initializer);
+
+        this.containerInitializer.onStartup(null, this.servletContext);
+
+        then(initializer).should().start();
+        then(initializer).should().setLoggerContext();
+        then(registration).should().setAsyncSupported(eq(true));
+        then(registration).should().addMappingForUrlPatterns(eq(EnumSet.allOf(DispatcherType.class)), eq(false), eq("/*"));
+
+        // initParam IS_LOG4J_AUTO_SHUTDOWN_DISABLED is "true" so addListener shouldn't be called.
+        then(servletContext).should(never()).addListener(any(Log4jServletContextListener.class));
+    }
+
+    @Test
     public void testOnStartupWithServletVersion3_xEffectiveVersion3_xDisabledTRUE() throws Exception {
         given(servletContext.getMajorVersion()).willReturn(3);
         given(servletContext.getEffectiveMajorVersion()).willReturn(3);
@@ -110,12 +135,13 @@
         then(registration).should().setAsyncSupported(eq(true));
         then(registration).should().addMappingForUrlPatterns(eq(EnumSet.allOf(DispatcherType.class)), eq(false), eq("/*"));
 
-        assertNotNull("The listener should not be null.", listenerCaptor.getValue());
-        assertSame("The listener is not correct.", Log4jServletContextListener.class,
-            listenerCaptor.getValue().getClass());
+        assertNotNull(listenerCaptor.getValue(), "The listener should not be null.");
+        assertSame(Log4jServletContextListener.class,
+            listenerCaptor.getValue().getClass(),
+            "The listener is not correct.");
 
-        assertNotNull("The filter should not be null.", filterCaptor.getValue());
-        assertSame("The filter is not correct.", Log4jServletFilter.class, filterCaptor.getValue());
+        assertNotNull(filterCaptor.getValue(), "The filter should not be null.");
+        assertSame(Log4jServletFilter.class, filterCaptor.getValue(), "The filter is not correct.");
     }
 
     @Test
@@ -128,8 +154,8 @@
 
         this.containerInitializer.onStartup(null, this.servletContext);
 
-        assertNotNull("The filter should not be null.", filterCaptor.getValue());
-        assertSame("The filter is not correct.", Log4jServletFilter.class, filterCaptor.getValue());
+        assertNotNull(filterCaptor.getValue(), "The filter should not be null.");
+        assertSame(Log4jServletFilter.class, filterCaptor.getValue(), "The filter is not correct.");
     }
 
     @Test
@@ -148,11 +174,11 @@
             this.containerInitializer.onStartup(null, this.servletContext);
             fail("Expected the exception thrown by the initializer; got no exception.");
         } catch (final IllegalStateException e) {
-            assertSame("The exception is not correct.", exception, e);
+            assertSame(exception, e, "The exception is not correct.");
         }
 
         then(initializer).should().start();
-        assertNotNull("The filter should not be null.", filterCaptor.getValue());
-        assertSame("The filter is not correct.", Log4jServletFilter.class, filterCaptor.getValue());
+        assertNotNull(filterCaptor.getValue(), "The filter should not be null.");
+        assertSame(Log4jServletFilter.class, filterCaptor.getValue(), "The filter is not correct.");
     }
 }
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
index 1dbc75d..35d662d 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
@@ -20,31 +20,33 @@
 import javax.servlet.ServletContextEvent;
 
 import org.apache.logging.log4j.util.Strings;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.BDDMockito.eq;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.then;
 import static org.mockito.BDDMockito.willThrow;
 
-import java.util.concurrent.TimeUnit;
-
-@RunWith(MockitoJUnitRunner.class)
+@ExtendWith(MockitoExtension.class)
 public class Log4jServletContextListenerTest {
-    @Mock
-    private ServletContextEvent event;
-    @Mock
+	/* event and servletContext are marked lenient because they aren't used in the
+	 * testDestroyWithNoInit but are only accessed during initialization
+	 */
+	@Mock(lenient = true)
+	private ServletContextEvent event;
+	@Mock(lenient = true)
     private ServletContext servletContext;
     @Mock
     private Log4jWebLifeCycle initializer;
 
     private Log4jServletContextListener listener;
 
-    @Before
+    @BeforeEach
     public void setUp() {
         this.listener = new Log4jServletContextListener();
         given(event.getServletContext()).willReturn(servletContext);
@@ -61,7 +63,7 @@
         this.listener.contextDestroyed(this.event);
 
         then(initializer).should().clearLoggerContext();
-        then(initializer).should().stop(30L, TimeUnit.SECONDS);
+        then(initializer).should().stop();
     }
 
     @Test
@@ -72,8 +74,36 @@
             this.listener.contextInitialized(this.event);
             fail("Expected a RuntimeException.");
         } catch (final RuntimeException e) {
-            assertEquals("The message is not correct.", "Failed to initialize Log4j properly.", e.getMessage());
+            assertEquals("Failed to initialize Log4j properly.", e.getMessage(), "The message is not correct.");
         }
     }
 
+    @Test
+    public void initializingLog4jServletContextListenerShouldFaileWhenAutoShutdownIsTrue() throws Exception {
+        given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED)))
+                .willReturn("true");
+        ensureInitializingFailsWhenAuthShutdownIsEnabled();
+    }
+
+    @Test
+    public void initializingLog4jServletContextListenerShouldFaileWhenAutoShutdownIsTRUE() throws Exception {
+        given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED)))
+                .willReturn("TRUE");
+        ensureInitializingFailsWhenAuthShutdownIsEnabled();
+    }
+    
+    private void ensureInitializingFailsWhenAuthShutdownIsEnabled() {
+        try {
+            this.listener.contextInitialized(this.event);
+            fail("Expected a RuntimeException.");
+        } catch (final RuntimeException e) {
+            String expectedMessage = 
+                    "Do not use " + Log4jServletContextListener.class.getSimpleName() + " when " 
+                    + Log4jWebSupport.IS_LOG4J_AUTO_SHUTDOWN_DISABLED + " is true. Please use " 
+                    + Log4jShutdownOnContextDestroyedListener.class.getSimpleName() + " instead of " 
+                    + Log4jServletContextListener.class.getSimpleName() + ".";
+
+            assertEquals(expectedMessage, e.getMessage(), "The message is not correct");
+        }
+    }
 }
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletFilterTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletFilterTest.java
index 1fb72a3..479ac26 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletFilterTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletFilterTest.java
@@ -22,23 +22,24 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.BeforeEach;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
 
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.then;
 import static org.mockito.Mockito.reset;
 
-@RunWith(MockitoJUnitRunner.class)
+@ExtendWith(MockitoExtension.class)
 public class Log4jServletFilterTest {
-    @Mock
+    @Mock(lenient = true) // because filterConfig is not used in testDestroy
     private FilterConfig filterConfig;
-    @Mock
+    @Mock(lenient = true) // because filterConfig is not used in testDestroy
     private ServletContext servletContext;
     @Mock
     private Log4jWebLifeCycle initializer;
@@ -51,7 +52,7 @@
 
     private Log4jServletFilter filter;
 
-    @Before
+    @BeforeEach
     public void setUp() {
         given(filterConfig.getServletContext()).willReturn(servletContext);
         given(servletContext.getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE)).willReturn(initializer);
@@ -69,9 +70,11 @@
         then(initializer).should().setLoggerContext();
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testDestroy() {
-        this.filter.destroy();
+    	assertThrows(IllegalStateException.class, () -> {
+    		this.filter.destroy();
+    	});
     }
 
     @Test
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListenerTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListenerTest.java
new file mode 100644
index 0000000..5577381
--- /dev/null
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jShutdownOnContextDestroyedListenerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.web;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.never;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class Log4jShutdownOnContextDestroyedListenerTest {
+    @Mock(lenient = true)
+    private ServletContextEvent event;
+    @Mock(lenient = true)
+    private ServletContext servletContext;
+    @Mock
+    private Log4jWebLifeCycle initializer;
+
+    private Log4jShutdownOnContextDestroyedListener listener;
+
+    public void setUp(boolean mockInitializer) {
+        this.listener = new Log4jShutdownOnContextDestroyedListener();
+        given(event.getServletContext()).willReturn(servletContext);
+        if (mockInitializer) {        	
+        	given(servletContext.getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE))
+        			.willReturn(initializer);
+        }
+    }
+		
+    @Test
+    public void testInitAndDestroy() throws Exception {
+    	setUp(true);
+        this.listener.contextInitialized(this.event);
+
+        then(initializer).should(never()).start();
+        then(initializer).should(never()).setLoggerContext();
+
+        this.listener.contextDestroyed(this.event);
+
+        then(initializer).should().clearLoggerContext();
+        then(initializer).should().stop();
+    }
+
+    @Test
+    public void testDestroy() throws Exception {
+    	setUp(true);
+        this.listener.contextDestroyed(this.event);
+
+        then(initializer).should(never()).clearLoggerContext();
+        then(initializer).should(never()).stop();
+    }
+    
+    @Test
+    public void whenNoInitializerInContextTheContextInitializedShouldThrowAnException() {
+    	setUp(false);
+    	
+    	assertThrows(IllegalStateException.class, () -> {
+    		this.listener.contextInitialized(this.event);
+    	});
+    }
+}
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jWebInitializerImplTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jWebInitializerImplTest.java
index 71d0cf2..40e10df 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jWebInitializerImplTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jWebInitializerImplTest.java
@@ -24,83 +24,80 @@
 import org.apache.logging.log4j.core.config.DefaultConfiguration;
 import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.then;
 
-@RunWith(MockitoJUnitRunner.class)
+@ExtendWith(MockitoExtension.class)
 public class Log4jWebInitializerImplTest {
-    @Mock
-    private ServletContext servletContext;
+	/* Marking servletContext lenient because otherwise testCompositeLocationParameterWithEmptyUriListSetsDefaultConfiguration fails
+	 * when null is passed in as the initial param because Mockito deciced null isn't a String rather than the absence of a string.
+	 */
+	@Mock(lenient = true)
+	private ServletContext servletContext;
     @Captor
     private ArgumentCaptor<Log4jWebLifeCycle> initializerCaptor;
     @Captor
     private ArgumentCaptor<LoggerContext> loggerContextCaptor;
-    @Rule
-    public ExpectedException expectedException = ExpectedException.none();
 
     private Log4jWebInitializerImpl initializerImpl;
 
-    @Before
+    @BeforeEach
     public void setUp() {
         given(servletContext.getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE)).willReturn(null);
 
         final Log4jWebLifeCycle initializer = WebLoggerContextUtils.getWebLifeCycle(this.servletContext);
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.SUPPORT_ATTRIBUTE), initializerCaptor.capture());
-        assertNotNull("The initializer should not be null.", initializer);
-        assertSame("The capture is not correct.", initializer, initializerCaptor.getValue());
-        assertTrue("The initializer is not correct.", initializer instanceof Log4jWebInitializerImpl);
+        assertNotNull(initializer, "The initializer should not be null.");
+        assertSame(initializer, initializerCaptor.getValue(), "The capture is not correct.");
+        assertTrue(initializer instanceof Log4jWebInitializerImpl, "The initializer is not correct.");
 
         this.initializerImpl = (Log4jWebInitializerImpl) initializer;
     }
 
     @Test
     public void testDeinitializeBeforeInitialize() {
-        expectedException.expect(IllegalStateException.class);
-        this.initializerImpl.stop();
+    	assertThrows(IllegalStateException.class, () -> {
+    		this.initializerImpl.stop();
+    	});
     }
 
     @Test
     public void testSetLoggerContextBeforeInitialize() {
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
     }
 
     @Test
     public void testClearLoggerContextBeforeInitialize() {
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.clearLoggerContext();
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
     }
 
     @Test
-    public void testInitializeWithNoParametersThenSetLoggerContextThenDeinitialize() throws Exception {
+    public void testInitializeWithNoParametersThenSetLoggerContextThenDeinitialize() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn(null);
@@ -110,85 +107,85 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
         final org.apache.logging.log4j.spi.LoggerContext loggerContext = loggerContextCaptor.getValue();
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
 
         this.initializerImpl.setLoggerContext();
 
         final LoggerContext context = ContextAnchor.THREAD_CONTEXT.get();
-        assertNotNull("The context should not be null.", context);
-        assertSame("The context is not correct.", loggerContext, context);
+        assertNotNull(context, "The context should not be null.");
+        assertSame(loggerContext, context, "The context is not correct.");
 
         this.initializerImpl.clearLoggerContext();
 
-        assertNull("The context should be null again.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null again.");
 
         this.initializerImpl.stop();
 
         then(servletContext).should().removeAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE));
 
-        assertNull("The context should again still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should again still be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should finally still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should finally still be null.");
     }
 
     @Test
-    public void testInitializeWithClassLoaderNoParametersThenSetLoggerContextThenDeinitialize() throws Exception {
+    public void testInitializeWithClassLoaderNoParametersThenSetLoggerContextThenDeinitialize() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn("false");
         given(servletContext.getServletContextName()).willReturn("helloWorld02");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
         given(servletContext.getClassLoader()).willReturn(getClass().getClassLoader());
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
         final org.apache.logging.log4j.spi.LoggerContext loggerContext = loggerContextCaptor.getValue();
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
 
         this.initializerImpl.setLoggerContext();
 
         final LoggerContext context = ContextAnchor.THREAD_CONTEXT.get();
-        assertNotNull("The context should not be null.", context);
-        assertSame("The context is not correct.", loggerContext, context);
+        assertNotNull(context, "The context should not be null.");
+        assertSame(loggerContext, context, "The context is not correct.");
 
         this.initializerImpl.clearLoggerContext();
 
-        assertNull("The context should be null again.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null again.");
 
         this.initializerImpl.stop();
 
         then(servletContext).should().removeAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE));
 
-        assertNull("The context should again still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should again still be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should finally still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should finally still be null.");
     }
 
     @Test
-    public void testInitializeIsIdempotent() throws Exception {
+    public void testInitializeIsIdempotent() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn("nothing");
         given(servletContext.getServletContextName()).willReturn("helloWorld03");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
         given(servletContext.getClassLoader()).willReturn(getClass().getClassLoader());
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         this.initializerImpl.start();
         this.initializerImpl.start();
@@ -199,42 +196,43 @@
     }
 
     @Test
-    public void testInitializeFailsAfterDeinitialize() throws Exception {
+    public void testInitializeFailsAfterDeinitialize() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn(null);
         given(servletContext.getServletContextName()).willReturn("helloWorld04");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
         given(servletContext.getClassLoader()).willReturn(getClass().getClassLoader());
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         this.initializerImpl.stop();
 
         then(servletContext).should().removeAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE));
 
-        expectedException.expect(IllegalStateException.class);
-        this.initializerImpl.start();
+    	assertThrows(IllegalStateException.class, () -> {
+    		this.initializerImpl.start();
+    	});
     }
 
     @Test
-    public void testDeinitializeIsIdempotent() throws Exception {
+    public void testDeinitializeIsIdempotent() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn(null);
         given(servletContext.getServletContextName()).willReturn("helloWorld05");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
         given(servletContext.getClassLoader()).willReturn(getClass().getClassLoader());
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         this.initializerImpl.stop();
         this.initializerImpl.stop();
@@ -243,88 +241,86 @@
     }
 
     @Test
-    public void testInitializeUsingJndiSelectorFails() throws Exception {
+    public void testInitializeUsingJndiSelectorFails() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn("true");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
-        expectedException.expect(IllegalStateException.class);
-        this.initializerImpl.start();
+    	assertThrows(IllegalStateException.class, () -> {
+    		this.initializerImpl.start();
+    	});
     }
 
     @Test
-    public void testInitializeUsingJndiSelector() throws Exception {
+    public void testInitializeUsingJndiSelector() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn("helloWorld06");
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn("true");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNull("The context attribute should be null.", loggerContextCaptor.getValue());
+        assertNull(loggerContextCaptor.getValue(), "The context attribute should be null.");
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should still be null because no named selector.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null because no named selector.");
 
         this.initializerImpl.clearLoggerContext();
 
-        assertNull("The context should be null again.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null again.");
 
         this.initializerImpl.stop();
 
-        assertNull("The context should again still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should again still be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should finally still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should finally still be null.");
     }
 
     @Test
-    public void testWrapExecutionWithNoParameters() throws Exception {
+    public void testWrapExecutionWithNoParameters() {
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONTEXT_NAME))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.LOG4J_CONFIG_LOCATION))).willReturn(null);
         given(servletContext.getInitParameter(eq(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))).willReturn(null);
         given(servletContext.getServletContextName()).willReturn("helloWorld07");
         given(servletContext.getResourcePaths("/WEB-INF/")).willReturn(null);
-        assertNull("The context should be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null.");
 
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
         final org.apache.logging.log4j.spi.LoggerContext loggerContext = loggerContextCaptor.getValue();
 
-        assertNull("The context should still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should still be null.");
 
-        final Runnable runnable = new Runnable() {
-            @Override
-            public void run() {
-                final LoggerContext context = ContextAnchor.THREAD_CONTEXT.get();
-                assertNotNull("The context should not be null.", context);
-                assertSame("The context is not correct.", loggerContext, context);
-            }
+        final Runnable runnable = () -> {
+            final LoggerContext context = ContextAnchor.THREAD_CONTEXT.get();
+            assertNotNull(context, "The context should not be null.");
+            assertSame(loggerContext, context, "The context is not correct.");
         };
 
         this.initializerImpl.wrapExecution(runnable);
 
-        assertNull("The context should be null again.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should be null again.");
 
         this.initializerImpl.stop();
 
         then(servletContext).should().removeAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE));
 
-        assertNull("The context should again still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should again still be null.");
 
         this.initializerImpl.setLoggerContext();
 
-        assertNull("The context should finally still be null.", ContextAnchor.THREAD_CONTEXT.get());
+        assertNull(ContextAnchor.THREAD_CONTEXT.get(), "The context should finally still be null.");
     }
 
     @Test
@@ -334,7 +330,7 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         assertThat(loggerContextCaptor.getValue().getConfigLocation(), is(nullValue()));
 
@@ -349,7 +345,7 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         assertThat(loggerContextCaptor.getValue().getConfigLocation(), is(URI.create("file:/a/b/c/WEB-INF/log4j2.xml")));
 
@@ -367,7 +363,7 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         assertThat(loggerContextCaptor.getValue().getConfigLocation(),
                 is(URI.create("file:/a/b/c/WEB-INF/log4j2-mycontext.xml")));
@@ -382,7 +378,7 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         assertThat(loggerContextCaptor.getValue().getConfiguration(), is(instanceOf(DefaultConfiguration.class)));
 
@@ -398,7 +394,7 @@
         this.initializerImpl.start();
 
         then(servletContext).should().setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE), loggerContextCaptor.capture());
-        assertNotNull("The context attribute should not be null.", loggerContextCaptor.getValue());
+        assertNotNull(loggerContextCaptor.getValue(), "The context attribute should not be null.");
 
         assertThat(loggerContextCaptor.getValue().getConfiguration(), is(instanceOf(CompositeConfiguration.class)));
 
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/PropertyTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/PropertyTest.java
index d17f495..40945c2 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/PropertyTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/PropertyTest.java
@@ -19,9 +19,9 @@
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
 import org.apache.logging.log4j.util.Constants;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  *
@@ -31,12 +31,12 @@
     @Test
     public void testShutdownHookDisabled() {
         assertFalse(
-                "Shutdown hook should be disabled by default in web applications",
-                ((Log4jContextFactory) LogManager.getFactory()).isShutdownHookEnabled());
+                ((Log4jContextFactory) LogManager.getFactory()).isShutdownHookEnabled(),
+                "Shutdown hook should be disabled by default in web applications");
     }
 
     @Test
     public void testIsWebApp() {
-        assertTrue("When servlet classes are available IS_WEB_APP should default to true", Constants.IS_WEB_APP);
+        assertTrue(Constants.IS_WEB_APP, "When servlet classes are available IS_WEB_APP should default to true");
     }
 }
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/ServletAppenderTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/ServletAppenderTest.java
index bb7147c..0317b02 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/ServletAppenderTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/ServletAppenderTest.java
@@ -24,10 +24,10 @@
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.springframework.mock.web.MockServletContext;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  *
@@ -50,12 +50,12 @@
             initializer.start();
             initializer.setLoggerContext();
             final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
-            assertNotNull("No LoggerContext", ctx);
-            assertNotNull("No ServletContext", WebLoggerContextUtils.getServletContext());
+            assertNotNull(ctx, "No LoggerContext");
+            assertNotNull(WebLoggerContextUtils.getServletContext(), "No ServletContext");
             final Configuration configuration = ctx.getConfiguration();
-            assertNotNull("No configuration", configuration);
+            assertNotNull(configuration, "No configuration");
             final Appender appender = configuration.getAppender("Servlet");
-            assertNotNull("No ServletAppender", appender);
+            assertNotNull(appender, "No ServletAppender");
             final Logger logger = LogManager.getLogger("Test");
             logger.info("This is a test");
             logger.error("This is a test 2", new IllegalStateException().fillInStackTrace());
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
index 398c4b4..e2c8ded 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
@@ -26,11 +26,11 @@
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockServletContext;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 public class WebLookupTest {
 
@@ -48,27 +48,27 @@
             initializer.start();
             initializer.setLoggerContext();
             final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
-            assertNotNull("No LoggerContext", ctx);
-            assertNotNull("No ServletContext", WebLoggerContextUtils.getServletContext());
+            assertNotNull(ctx, "No LoggerContext");
+            assertNotNull(WebLoggerContextUtils.getServletContext(), "No ServletContext");
             final Configuration config = ctx.getConfiguration();
-            assertNotNull("No Configuration", config);
+            assertNotNull(config, "No Configuration");
             final StrSubstitutor substitutor = config.getStrSubstitutor();
-            assertNotNull("No Interpolator", substitutor);
+            assertNotNull(substitutor, "No Interpolator");
             String value = substitutor.replace("${web:initParam.TestParam}");
-            assertNotNull("No value for TestParam", value);
-            assertEquals("Incorrect value for TestParam: " + value, "ParamValue", value);
+            assertNotNull(value, "No value for TestParam");
+            assertEquals("ParamValue", value, "Incorrect value for TestParam: " + value);
             value = substitutor.replace("${web:attr.TestAttr}");
-            assertNotNull("No value for TestAttr", value);
-            assertEquals("Incorrect value for TestAttr: " + value, "AttrValue", value);
+            assertNotNull(value, "No value for TestAttr");
+            assertEquals("AttrValue", value, "Incorrect value for TestAttr: " + value);
             value = substitutor.replace("${web:Name1}");
-            assertNotNull("No value for Name1", value);
-            assertEquals("Incorrect value for Name1: " + value, "Ben", value);
+            assertNotNull(value, "No value for Name1");
+            assertEquals("Ben", value, "Incorrect value for Name1: " + value);
             value = substitutor.replace("${web:Name2}");
-            assertNotNull("No value for Name2", value);
-            assertEquals("Incorrect value for Name2: " + value, "Jerry", value);
+            assertNotNull(value, "No value for Name2");
+            assertEquals("Jerry", value, "Incorrect value for Name2: " + value);
             value = substitutor.replace("${web:contextPathName}");
-            assertNotNull("No value for context name", value);
-            assertEquals("Incorrect value for context name", "WebApp", value);
+            assertNotNull(value, "No value for context name");
+            assertEquals("WebApp", value, "Incorrect value for context name");
         } catch (final IllegalStateException e) {
             fail("Failed to initialize Log4j properly." + e.getMessage());
         }
@@ -90,10 +90,10 @@
         initializer.start();
         initializer.setLoggerContext();
         final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
-        assertNotNull("No LoggerContext", ctx);
-        assertNotNull("No ServletContext", WebLoggerContextUtils.getServletContext());
+        assertNotNull(ctx, "No LoggerContext");
+        assertNotNull(WebLoggerContextUtils.getServletContext(), "No ServletContext");
         final Configuration config = ctx.getConfiguration();
-        assertNotNull("No Configuration", config);
+        assertNotNull(config, "No Configuration");
         final Map<String, Appender> appenders = config.getAppenders();
         for (final Map.Entry<String, Appender> entry : appenders.entrySet()) {
             if (entry.getKey().equals("file")) {
@@ -103,8 +103,8 @@
         }
         final StrSubstitutor substitutor = config.getStrSubstitutor();
         String value = substitutor.replace("${web:contextPathName:-default}");
-        assertNotNull("No value for context name", value);
-        assertEquals("Incorrect value for context name", "default", value);
+        assertEquals("default", value, "Incorrect value for context name");
+        assertNotNull(value, "No value for context name");
         initializer.stop();
         ContextAnchor.THREAD_CONTEXT.remove();
     }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 06f9c96..9ca3f47 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,10 +31,20 @@
          - "remove" - Removed
     -->
     <release version="3.0.0" date="2021-MM-DD" description="GA Release 3.0.0">
-      <action issue="LOG4J2-2977" dev="rgrabowski" type="fix">
+      <action issue="LOG4J2-2624" dev="mattsicker" type="fix" due-to="Tim Perry">
+        Allow auto-shutdown of log4j in log4j-web to be turned off and provide a
+        ServletContextListener "Log4jShutdownOnContextDestroyedListener" to stop log4j.
+        Register the listener at the top of web.xml to ensure the shutdown happens last.
+      </action>
+      <action issue="LOG4J2-1606" dev="mattsicker" type="fix" due-to="Tim Perry">
+        Allow auto-shutdown of log4j in log4j-web to be turned off and provide a
+        ServletContextListener "Log4jShutdownOnContextDestroyedListener" to stop log4j.
+        Register the listener at the top of web.xml to ensure the shutdown happens last.
+      </action>
+      <action issue="LOG4J2-2977" dev="vy" due-to="Ron Grabowski">
         Replace outdated PatternLayout.createLayout() calls in docs with createDefaultLayout().
       </action>
-      <action issue="LOG4J2-2973" dev="fricchiuti" type="fix">
+      <action issue="LOG4J2-2973" dev="vy" type="fix" due-to="Fabio Ricchiuti">
         Rename EventTemplateAdditionalField#type (conflicting with properties file parser) to "format".
       </action>
       <action issue="LOG4J2-2999" dev="vy" type="add">