diff --git a/build.xml b/build.xml
index acde4722de..dc1ea4375e 100644
--- a/build.xml
+++ b/build.xml
@@ -86,12 +86,10 @@
         <property name="pmd.dir" location="${tools.dir}/pmd"/>
         <property name="checkstyle.dir" location="${tools.dir}/checkstyle"/>
         <property name="spotbugs.dir" location="${tools.dir}/spotbugs"/>
-        <property name="javacc.home" location="${tools.dir}"/>
         <property name="mapcss.dir" location="${src.dir}/org/openstreetmap/josm/gui/mappaint/mapcss"/>
         <property name="proj-build.dir" location="${base.dir}/build2"/>
         <property name="script-build.dir" location="${base.dir}/build2"/>
         <property name="epsg.output" location="${resources.dir}/data/projection/custom-epsg"/>
-        <property name="commons-lang3.jar" location="${tools.dir}/commons-lang3.jar"/>
         <property name="dist.jar" location="${dist.dir}/josm-custom.jar"/>
         <property name="dist-optimized.jar" location="${dist.dir}/josm-custom-optimized.jar"/>
         <property name="dist-sources.jar" location="${dist.dir}/josm-custom-sources.jar"/>
@@ -305,7 +303,6 @@ Build-Date: ${build.tstamp}
     </macrodef>
     <target name="compile" depends="init,javacc" unless="compile.notRequired" description="Compile JOSM">
         <ivy:cachepath log="download-only" file="${tools.ivy}" pathid="errorprone.classpath" conf="errorprone"/>
-        <ivy:cachepath log="download-only" file="${tools.ivy}" pathid="errorprone_javac.classpath" conf="errorprone_javac"/>
         <!-- JOSM -->
         <call-javac-compile excludes="**/package-info.java,**/module-info.java,main/**" release="${java.lang.version}"/>
         <!-- Java 11 specific files (2018-09 LTS) -->
@@ -404,7 +401,6 @@ Build-Date: ${build.tstamp}
         </path>
         <ivy:retrieve log="download-only" pattern="${test.dir}/lib/[artifact].[ext]" conf="junit5-system-exit"/>
         <ivy:retrieve log="download-only" pattern="${test.dir}/lib/[artifact].[ext]" conf="jmockit"/>
-        <ivy:retrieve log="download-only" pattern="${tools.dir}/[artifact].[ext]" conf="commonslang"/>
         <ivy:retrieve log="download-only" pattern="${tools.dir}/[conf].[ext]" conf="jacocoant"/>
         <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml" classpath="${tools.dir}/jacocoant.jar" />
     </target>
@@ -469,7 +465,7 @@ Build-Date: ${build.tstamp}
             <junitlauncher printsummary="${junit.printsummary}" failureproperty="test.@{testfamily}@{testITsuffix}.failed">
                 <classpath>
                     <path refid="test.classpath"/>
-                    <pathelement path="${test.dir}/build/unit"/> <!-- required for functional/etc to have JOSMTestRules -->
+                    <pathelement path="${test.dir}/build/unit"/> <!-- required for functional/etc to have the shared test utilities -->
                     <pathelement path="${test.dir}/build/@{testfamily}"/>
                 </classpath>
                 <testclasses outputDir="${test.dir}/report">
@@ -609,6 +605,10 @@ Build-Date: ${build.tstamp}
         -dontwarn com.google.errorprone.annotations.**
         -dontwarn java.lang.invoke.MethodHandle
 
+        # commons-lang3 is left out of the jar (see extract-libraries); CompressException still refers to it
+        # from a method none of the shipped classes call.
+        -dontwarn org.apache.commons.lang3.**
+
         # These options probably are not necessary (and make processing a bit slower)
         -dontskipnonpubliclibraryclasses
         -dontskipnonpubliclibraryclassmembers
@@ -1056,11 +1056,10 @@ Build-Date: ${build.tstamp}
         </exec>
         <move file="${modules.dir}/dots/summary.dot.png" tofile="${modules.dir}/josm-with-direct-dependencies.png"/>
         <!-- All dependencies -->
-        <jar basedir="${build.dir}" level="${clevel}" destfile="${modules.dir}/google-gdata.jar" includes="com/google/**/*.class"/>
         <path id="jar-all.files">
             <fileset dir="${modules.dir}" includes="*.jar"/>
         </path>
-        <pathconvert property="jar-all.files.string" refid="jar-direct.files" pathsep=" "/>
+        <pathconvert property="jar-all.files.string" refid="jar-all.files" pathsep=" "/>
         <exec executable="jdeps" dir="${modules.dir}">
             <arg line="-dotoutput dots"/>
             <arg line="--multi-release 11"/>
@@ -1079,7 +1078,7 @@ Build-Date: ${build.tstamp}
         <ivy:cachefileset log="download-only" setid="runtime.fileset" conf="runtime"/>
         <ivy:cachepath log="download-only" pathid="test.path" conf="test"/>
     </target>
-    <target name="extract-libraries" depends="resolve" description="Extract libraries to build dir">
+    <target name="extract-libraries" depends="init-properties,resolve" description="Extract libraries to build dir">
         <unzip dest="${build.dir}">
             <fileset refid="runtime.fileset"/>
             <patternset>
@@ -1135,6 +1134,7 @@ Build-Date: ${build.tstamp}
                 <exclude name="org/apache/commons/compress/compressors/CompressorException*"/>
                 <exclude name="org/apache/commons/compress/compressors/FileNameUtil*"/>
                 <exclude name="org/apache/commons/compress/compressors/deflate/**"/>
+                <exclude name="org/apache/commons/compress/compressors/deflate64/**"/>
                 <exclude name="org/apache/commons/compress/compressors/gzip/**"/>
                 <exclude name="org/apache/commons/compress/compressors/lz4/**"/>
                 <exclude name="org/apache/commons/compress/compressors/lzma/**"/>
@@ -1156,6 +1156,12 @@ Build-Date: ${build.tstamp}
                 <exclude name="org/apache/commons/jcs3/log/Log4j2Factory*"/>
                 <exclude name="org/apache/commons/jcs3/log/Log4j2LogAdapter*"/>
                 <exclude name="org/apache/commons/jcs3/utils/servlet/**"/>
+                <!-- commons-lang3 comes in transitively via commons-compress, but nothing we ship
+                     calls into it: the only two references are CompressException.requireNonNull()
+                     (never invoked by the classes left after the excludes above) and the deflate64
+                     decompressor (excluded). The Maven build leaves it out too, by declaring
+                     commons-lang3 as test-scoped in nodist/pom.xml. -->
+                <exclude name="org/apache/commons/lang3/**"/>
             </patternset>
         </unzip>
     </target>
diff --git a/ivy.xml b/ivy.xml
index 297a24c938..d4c00ca6bf 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -9,14 +9,13 @@
         <conf name="runtimeOnly" description="The libs shipped with JOSM that we do not need during compilation"/>
         <conf name="provided" description="The libs we need during compilation but not on application start"/>
         <!-- Special configurations used to retrieve jar files of test lib/tools referred by file path -->
-        <conf name="commonslang" description="Apache Commons Lang test library"/>
         <conf name="jmockit" description="JMockit test library"/>
         <conf name="jacocoant" description="Jacoco Ant test library"/>
         <conf name="junit5-system-exit" description="Everything needed for running junit5-system-exit as a javaagent"/>
         <!--  Meta configurations used in build scripts -->
         <conf name="runtime" description="Libraries only needed at runtime" extends="implementation,api,runtimeOnly"/>
         <conf name="compile" description="Libraries needed only for compilation" extends="implementation,api,provided"/>
-        <conf name="test" description="Libraries only needed for testing" extends="compile,runtime,commonslang,jmockit,junit5-system-exit"/>
+        <conf name="test" description="Libraries only needed for testing" extends="compile,runtime,jmockit,junit5-system-exit"/>
         <conf name="sources" description="Source code of used libraries"/>
     </configurations>
     <dependencies>
@@ -32,7 +31,7 @@
         <dependency conf="api->default" org="com.adobe.xmp" name="xmpcore" rev="6.1.11"/>
         <dependency conf="api->default" org="com.drewnoakes" name="metadata-extractor" rev="2.19.0" transitive="false"/>
         <dependency conf="api->default" org="com.github.weisj" name="jsvg" rev="2.1.0"/>
-        <dependency conf="api->default" org="org.jetbrains" name="annotations" rev="24.1.0"/>
+        <dependency conf="provided->default" org="org.jetbrains" name="annotations" rev="24.1.0"/>
         <dependency conf="api->default" org="ch.poole" name="OpeningHoursParser" rev="0.29.0"/>
         <!-- Don't forget to update org.openstreetmap.josm.tools.Tag2Link#PREF_SOURCE -->
         <dependency conf="api->default" org="org.webjars.npm" name="tag2link" rev="2026.6.26"/>
@@ -42,7 +41,7 @@
         <dependency conf="sources->sources" org="org.eclipse.parsson" name="parsson" rev="1.1.7"/>
         <dependency conf="sources->sources" org="org.apache.commons" name="commons-jcs3-core" rev="3.2.1"/>
         <dependency conf="sources->sources" org="org.apache.commons" name="commons-compress" rev="1.28.0"/>
-        <dependency conf="sources->sources" org="jakarta.annotation" name="jakarta.annotation-api" rev="2.1.1" />
+        <dependency conf="sources->sources" org="jakarta.annotation" name="jakarta.annotation-api" rev="3.0.0" />
         <dependency conf="sources->sources" org="org.tukaani" name="xz" rev="1.11"/>
         <dependency conf="sources->sources" org="com.adobe.xmp" name="xmpcore" rev="6.1.11"/>
         <dependency conf="sources->sources" org="com.drewnoakes" name="metadata-extractor" rev="2.19.0" transitive="false"/>
@@ -50,8 +49,6 @@
         <dependency conf="sources->sources" org="org.jetbrains" name="annotations" rev="24.1.0"/>
         <dependency conf="sources->sources" org="ch.poole" name="OpeningHoursParser" rev="0.29.0"/>
         <dependency conf="sources->default" org="org.webjars.npm" name="tag2link" rev="2026.6.26"/><!-- sources->default sic! (tag2link-sources.jar is empty, see #19335) -->
-        <!-- commonslang->default -->
-        <dependency conf="commonslang->default" org="org.apache.commons" name="commons-lang3" rev="3.18.0"/>
         <!-- jacocoant->default -->
         <dependency conf="jacocoant->default" org="org.jacoco" name="org.jacoco.ant" rev="0.8.13">
             <artifact name="org.jacoco.ant" type="jar" maven:classifier="nodeps"/>
@@ -64,11 +61,9 @@
         <dependency conf="test->default" org="io.github.classgraph" name="classgraph" rev="4.8.184"/>
         <dependency conf="test->default" org="org.junit.platform" name="junit-platform-launcher" rev="1.14.2"/>
         <dependency conf="test->default" org="org.junit.platform" name="junit-platform-suite" rev="1.14.2"/>
-        <dependency conf="test->default" org="org.junit.vintage" name="junit-vintage-engine" rev="5.14.2"/>
         <dependency conf="test->default" org="org.junit.jupiter" name="junit-jupiter-params" rev="5.14.2"/>
         <dependency conf="test->default" org="org.junit.jupiter" name="junit-jupiter-api" rev="5.14.2"/>
         <dependency conf="test->default" org="org.junit.jupiter" name="junit-jupiter-engine" rev="5.14.2"/>
-        <dependency conf="test->default" org="org.junit.jupiter" name="junit-jupiter-migrationsupport" rev="5.14.2"/>
         <dependency conf="test->default" org="net.trajano.commons" name="commons-testing" rev="2.1.0"/>
         <dependency conf="test->default" org="nl.jqno.equalsverifier" name="equalsverifier" rev="${versions.equalsverifier}"/>
         <dependency conf="test->default" org="org.apache.commons" name="commons-lang3" rev="3.20.0"/>
diff --git a/nodist/pom.xml b/nodist/pom.xml
index d2f732f38a..af7c94c71d 100644
--- a/nodist/pom.xml
+++ b/nodist/pom.xml
@@ -376,7 +376,7 @@
             <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-lang3</artifactId>
-                <version>3.18.0</version>
+                <version>3.20.0</version>
                 <scope>test</scope>
             </dependency>
             <dependency>
diff --git a/pom.xml b/pom.xml
index e444470c45..b1a7cb1f24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,6 @@
     <spotbugs.dir>${tools.dir}/spotbugs</spotbugs.dir>
     <tools.dir>${project.basedir}/tools</tools.dir>
     <epsg.output>${resources.dir}/data/projection/custom-epsg</epsg.output>
-    <javacc.home>${tools.dir}</javacc.home>
     <dist.jar>${dist.dir}/josm-custom.jar</dist.jar>
     <dist-optimized.jar>${dist.dir}/josm-custom-optimized.jar</dist-optimized.jar>
     <dist-sources.jar>${dist.dir}/josm-custom-sources.jar</dist-sources.jar>
@@ -175,11 +174,6 @@
       <artifactId>junit-platform-suite</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.junit.vintage</groupId>
-      <artifactId>junit-vintage-engine</artifactId>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-params</artifactId>
@@ -195,11 +189,6 @@
       <artifactId>junit-jupiter-engine</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-migrationsupport</artifactId>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>net.trajano.commons</groupId>
       <artifactId>commons-testing</artifactId>
@@ -714,6 +703,7 @@
                     <exclude>org/apache/commons/compress/compressors/CompressorException*</exclude>
                     <exclude>org/apache/commons/compress/compressors/FileNameUtil*</exclude>
                     <exclude>org/apache/commons/compress/compressors/deflate/**</exclude>
+                    <exclude>org/apache/commons/compress/compressors/deflate64/**</exclude>
                     <exclude>org/apache/commons/compress/compressors/gzip/**</exclude>
                     <exclude>org/apache/commons/compress/compressors/lz4/**</exclude>
                     <exclude>org/apache/commons/compress/compressors/lzma/**</exclude>
diff --git a/test/data/META-INF/services/org.junit.jupiter.api.extension.Extension b/test/data/META-INF/services/org.junit.jupiter.api.extension.Extension
index 7750cfa442..a754258446 100644
--- a/test/data/META-INF/services/org.junit.jupiter.api.extension.Extension
+++ b/test/data/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -1,4 +1,4 @@
-# These are the default extensions to run (all tests with JOSMTestRules would have done these by default)
+# These are the default extensions to run for every test
 org.openstreetmap.josm.testutils.annotations.BasicPreferences$BasicPreferencesExtension
 org.openstreetmap.josm.testutils.annotations.HTTP$HTTPExtension
 org.openstreetmap.josm.testutils.annotations.I18n$I18nExtension
diff --git a/test/pom.xml b/test/pom.xml
index cb0a356603..07ee885731 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -55,11 +55,6 @@
             <artifactId>junit-platform-suite</artifactId>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>org.junit.vintage</groupId>
-            <artifactId>junit-vintage-engine</artifactId>
-            <scope>compile</scope>
-        </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-params</artifactId>
@@ -75,11 +70,6 @@
             <artifactId>junit-jupiter-engine</artifactId>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-migrationsupport</artifactId>
-            <scope>compile</scope>
-        </dependency>
         <dependency>
             <groupId>net.trajano.commons</groupId>
             <artifactId>commons-testing</artifactId>
diff --git a/test/unit/org/openstreetmap/josm/JOSMFixture.java b/test/unit/org/openstreetmap/josm/JOSMFixture.java
index 5344ed5c99..368fcbb034 100644
--- a/test/unit/org/openstreetmap/josm/JOSMFixture.java
+++ b/test/unit/org/openstreetmap/josm/JOSMFixture.java
@@ -16,6 +16,7 @@ import java.util.TimeZone;
 import org.openstreetmap.josm.actions.DeleteAction;
 import org.openstreetmap.josm.command.DeleteCommand;
 import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
 import org.openstreetmap.josm.data.preferences.JosmUrls;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
@@ -29,7 +30,6 @@ import org.openstreetmap.josm.io.CertificateAmendment;
 import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.spi.lifecycle.Lifecycle;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.tools.Http1Client;
 import org.openstreetmap.josm.tools.HttpClient;
 import org.openstreetmap.josm.tools.I18n;
@@ -158,8 +158,24 @@ public class JOSMFixture {
             || url.startsWith("https://www.openstreetmap.org") || url.startsWith("https://api.openstreetmap.org");
     }
 
+    /**
+     * Cleans the Layer manager and the SelectionEventManager.
+     * You don't need to call this during tests, the test environment will do it for you.
+     * @since 12070
+     */
+    public static void cleanLayerEnvironment() {
+        // Get the instance before cleaning - this ensures that it is initialized.
+        SelectionEventManager eventManager = SelectionEventManager.getInstance();
+        MainApplication.getLayerManager().resetState();
+        try {
+            eventManager.resetState();
+        } catch (IllegalArgumentException e) {
+            Logging.trace(e);
+        }
+    }
+
     private void setupGUI() {
-        JOSMTestRules.cleanLayerEnvironment();
+        cleanLayerEnvironment();
         assertTrue(MainApplication.getLayerManager().getLayers().isEmpty());
         assertNull(MainApplication.getLayerManager().getEditLayer());
         assertNull(MainApplication.getLayerManager().getActiveLayer());
diff --git a/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java b/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java
index a3478870ee..8e61a167a1 100644
--- a/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java
+++ b/test/unit/org/openstreetmap/josm/data/cache/HostLimitQueueTest.java
@@ -4,6 +4,7 @@ package org.openstreetmap.josm.data.cache;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -13,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.commons.jcs3.access.behavior.ICacheAccess;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
 import org.openstreetmap.josm.data.imagery.TileJobOptions;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
@@ -71,9 +73,9 @@ class HostLimitQueueTest {
      * @throws Exception in case of error
      */
     @Test
-    void testSingleThreadPerHost() throws Exception {
+    void testSingleThreadPerHost(@TempDir File tempDir) throws Exception {
         ThreadPoolExecutor tpe = TMSCachedTileLoader.getNewThreadPoolExecutor("test-%d", 3, 1);
-        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testHostLimitQueue", 3, 0, "");
+        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testSingleThreadPerHost", 3, 0, tempDir.getPath());
         AtomicInteger counter = new AtomicInteger(0);
         long start = System.currentTimeMillis();
         for (int i = 0; i < 10; i++) {
@@ -97,9 +99,9 @@ class HostLimitQueueTest {
      * @throws Exception in case of error
      */
     @Test
-    void testMultipleThreadPerHost() throws Exception {
+    void testMultipleThreadPerHost(@TempDir File tempDir) throws Exception {
         ThreadPoolExecutor tpe = TMSCachedTileLoader.getNewThreadPoolExecutor("test-%d", 3, 2);
-        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testHostLimitQueue", 3, 0, "");
+        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testMultipleThreadPerHost", 3, 0, tempDir.getPath());
         AtomicInteger counter = new AtomicInteger(0);
         long start = System.currentTimeMillis();
         for (int i = 0; i < 10; i++) {
@@ -122,9 +124,9 @@ class HostLimitQueueTest {
      * @throws Exception in case of error
      */
     @Test
-    void testTwoHosts() throws Exception {
+    void testTwoHosts(@TempDir File tempDir) throws Exception {
         ThreadPoolExecutor tpe = TMSCachedTileLoader.getNewThreadPoolExecutor("test-%d", 3, 1);
-        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testHostLimitQueue", 3, 0, "");
+        ICacheAccess<String, CacheEntry> cache = JCSCacheManager.getCache("testTwoHosts", 3, 0, tempDir.getPath());
         AtomicInteger counter = new AtomicInteger(0);
         long start = System.currentTimeMillis();
         for (int i = 0; i < 10; i++) {
diff --git a/test/unit/org/openstreetmap/josm/data/cache/JCSCacheManagerTest.java b/test/unit/org/openstreetmap/josm/data/cache/JCSCacheManagerTest.java
index b5c97d907a..118d64b139 100644
--- a/test/unit/org/openstreetmap/josm/data/cache/JCSCacheManagerTest.java
+++ b/test/unit/org/openstreetmap/josm/data/cache/JCSCacheManagerTest.java
@@ -12,6 +12,7 @@ import org.apache.commons.jcs3.access.CacheAccess;
 import org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
 
 import net.trajano.commons.testing.UtilityClassTestUtil;
@@ -36,16 +37,16 @@ class JCSCacheManagerTest {
      * @throws IOException if any I/O error occurs
      */
     @Test
-    void testLoggingAdaptor12054() throws IOException {
-        JCSCacheManager.getCache("foobar", 1, 0, "foobar"); // cause logging adaptor to be initialized
+    void testLoggingAdaptor12054(@TempDir File tempDir) throws IOException {
+        JCSCacheManager.getCache("foobar", 1, 0, tempDir.getPath()); // cause logging adaptor to be initialized
         Logger.getLogger("org.apache.commons.jcs3").warning("{switch:0}");
     }
 
     @Test
-    void testUseBigDiskFile() throws IOException {
+    void testUseBigDiskFile(@TempDir File tempDir) throws IOException {
         if (JCSCacheManager.USE_BLOCK_CACHE.get()) {
             // test only when using block cache
-            File cacheFile = new File("foobar/testUseBigDiskFile_BLOCK_v2.data");
+            File cacheFile = new File(tempDir, "testUseBigDiskFile_BLOCK_v2.data");
             if (!cacheFile.exists()) {
                 if (!cacheFile.createNewFile()) {
                     System.err.println("Unable to create " + cacheFile.getAbsolutePath());
@@ -56,7 +57,7 @@ class JCSCacheManagerTest {
                 fileOutputStream.write(new byte[1024*1024*10]); // create 10MB empty file
             }
 
-            CacheAccess<Object, Object> cache = JCSCacheManager.getCache("testUseBigDiskFile", 1, 100, "foobar");
+            CacheAccess<Object, Object> cache = JCSCacheManager.getCache("testUseBigDiskFile", 1, 100, tempDir.getPath());
             assertEquals(10*1024,
                     ((BlockDiskCacheAttributes) cache.getCacheControl().getAuxCacheList().get(0).getAuxiliaryCacheAttributes()).getMaxKeySize(),
                     "BlockDiskCache use file size to calculate its size");
diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java
index 8805877ca0..7913474fdb 100644
--- a/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java
+++ b/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java
@@ -597,12 +597,6 @@ class MinimapDialogTest {
                 "Viewport marker not vertically centered"
                 );
 
-        try {
-            javax.imageio.ImageIO.write(paintedSlippyMap, "png", new java.io.File("failed.png"));
-        } catch (java.io.IOException ioe) {
-            System.err.println("Failed writing image");
-        }
-
         assertTrue(
                 Math.abs(colMatcher.group(2).length() - (rowMatcher.group(2).length()*2.0)) < 5,
                 "Viewport marker not 2:1 aspect ratio"
diff --git a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java
index 53fe63a3aa..bab01fcd41 100644
--- a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java
+++ b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java
@@ -30,6 +30,7 @@ import org.openstreetmap.josm.testutils.annotations.FullPreferences;
 import org.openstreetmap.josm.testutils.annotations.Main;
 import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker;
 import org.openstreetmap.josm.testutils.mockers.HelpAwareOptionPaneMocker;
+import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
 
 import com.github.tomakehurst.wiremock.client.WireMock;
 import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
@@ -54,6 +55,10 @@ class PluginHandlerJOSMTooOldTest {
      */
     @BeforeEach
     public void setUp() {
+        // not in a field initializer: those run before the extensions have switched the locale over to english
+        this.bazPluginVersionReqString = u202f("JOSM version 8\u202F001 required for plugin baz_plugin.");
+        this.dummyPluginVersionReqString = u202f("JOSM version 7\u202F001 required for plugin dummy_plugin.");
+
         Config.getPref().putInt("pluginmanager.version", 999);
         Config.getPref().put("pluginmanager.lastupdate", "999");
         Config.getPref().putList("pluginmanager.sites",
@@ -93,11 +98,20 @@ class PluginHandlerJOSMTooOldTest {
     private File targetQuxJar;
     private File targetQuxJarNew;
 
-    private final String bazPluginVersionReqString = u202f("JOSM version 8\u202F001 required for plugin baz_plugin.");
-    private final String dummyPluginVersionReqString = u202f("JOSM version 7\u202F001 required for plugin dummy_plugin.");
+    private String bazPluginVersionReqString;
+    private String dummyPluginVersionReqString;
     private final String dummyPluginFailedString = "<html>Updating the following plugin has failed:<ul><li>dummy_plugin</li></ul>"
         + "Please open the Preference Dialog after JOSM has started and try to update it manually.</html>";
 
+    /**
+     * Replace the digit group separator of {@link JosmDecimalFormatSymbolsProvider} with the one actually in effect.
+     * <p>
+     * This must not be called before the locale has been set up for the test: the provider only covers the locales
+     * listed in {@link JosmDecimalFormatSymbolsProvider#getAvailableLocales}, so on a machine whose default locale
+     * is not one of them (say {@code hu_HU}) it would answer with the platform separator instead.
+     * @param s the expected message, using {@code U+202F} as the digit group separator
+     * @return {@code s} with the digit group separator in effect substituted
+     */
     private static String u202f(String s) {
         return s.replace('\u202F', DecimalFormatSymbols.getInstance().getGroupingSeparator());
     }
diff --git a/test/unit/org/openstreetmap/josm/testutils/ExpectedRootException.java b/test/unit/org/openstreetmap/josm/testutils/ExpectedRootException.java
deleted file mode 100644
index 21516d6787..0000000000
--- a/test/unit/org/openstreetmap/josm/testutils/ExpectedRootException.java
+++ /dev/null
@@ -1,119 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.testutils;
-
-import static org.openstreetmap.josm.testutils.ThrowableRootCauseMatcher.hasRootCause;
-
-import org.hamcrest.Matcher;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * The {@code ExpectedRootException} behaves exactly as JUnit's {@link ExpectedException} rule.
- * This class is needed to add {@link #expectRootCause} method, which has been rejected by JUnit developers,
- * and {@code ExpectedException} cannot be extended because it has a private constructor.
- * @see <a href="https://github.com/junit-team/junit4/pull/778">Github pull request</a>
- * @deprecated Use matchers instead with the return from {@link org.junit.jupiter.api.Assertions#assertThrows}
- */
-@Deprecated
-public final class ExpectedRootException implements TestRule {
-
-    private final ExpectedException rule = ExpectedException.none();
-
-    /**
-     * Returns a {@linkplain TestRule rule} that expects no exception to be thrown (identical to behavior without this rule).
-     * @return {@code ExpectedRootException} instance
-     */
-    @SuppressFBWarnings("NM_CLASS_NOT_EXCEPTION")
-    public static ExpectedRootException none() {
-        return new ExpectedRootException();
-    }
-
-    private ExpectedRootException() {
-    }
-
-    /**
-     * Specifies the failure message for tests that are expected to throw an exception but do not throw any.
-     * You can use a {@code %s} placeholder for the description of the expected exception.
-     * E.g. "Test doesn't throw %s." will fail with the error message "Test doesn't throw an instance of foo.".
-     *
-     * @param message exception detail message
-     * @return the rule itself
-     */
-    public ExpectedRootException reportMissingExceptionWithMessage(String message) {
-        rule.reportMissingExceptionWithMessage(message);
-        return this;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return rule.apply(base, description);
-    }
-
-    /**
-     * Verify that your code throws an exception that is an instance of specific {@code type}.
-     * <pre> &#064;Test
-     * public void throwsExceptionWithSpecificType() {
-     *     thrown.expect(NullPointerException.class);
-     *     throw new NullPointerException();
-     * }</pre>
-     * @param type Throwable type
-     * @return {@code this}
-     */
-    public ExpectedRootException expect(Class<? extends Throwable> type) {
-        rule.expect(type);
-        return this;
-    }
-
-    /**
-     * Verify that your code throws an exception whose message contains a specific text.
-     * <pre> &#064;Test
-     * public void throwsExceptionWhoseMessageContainsSpecificText() {
-     *     thrown.expectMessage(&quot;happened&quot;);
-     *     throw new NullPointerException(&quot;What happened?&quot;);
-     * }</pre>
-     * @param substring substring to expect in error message
-     * @return {@code this}
-     */
-    public ExpectedRootException expectMessage(String substring) {
-        rule.expectMessage(substring);
-        return this;
-    }
-
-    /**
-     * Verify that your code throws an exception whose immediate cause is matched by the given Hamcrest matcher.
-     * <pre> &#064;Test
-     * public void throwsExceptionWhoseCauseCompliesWithMatcher() {
-     *     NullPointerException rootCause = new NullPointerException();
-     *     IllegalStateException immediateCause = new IllegalStateException(rootCause);
-     *     thrown.expectCause(isA(NullPointerException.class));
-     *     throw new IllegalArgumentException(immediateCause);
-     * }</pre>
-     * @param expectedCause expected cause
-     * @return {@code this}
-     */
-    public ExpectedRootException expectCause(Matcher<? extends Throwable> expectedCause) {
-        rule.expectCause(expectedCause);
-        return this;
-    }
-
-    /**
-     * Verify that you code throws an exception whose root cause is matched by the given Hamcrest matcher.
-     * <pre> &#064;Test
-     * public void throwsExceptionWhoseRootCauseCompliesWithMatcher() {
-     *     NullPointerException rootCause = new NullPointerException();
-     *     IllegalStateException immediateCause = new IllegalStateException(rootCause);
-     *     thrown.expectRootCause(isA(NullPointerException.class));
-     *     throw new IllegalArgumentException(immediateCause);
-     * }</pre>
-     * @param expectedRootCause expected root cause
-     * @return {@code this}
-     */
-    public ExpectedRootException expectRootCause(Matcher<? extends Throwable> expectedRootCause) {
-        rule.expect(hasRootCause(expectedRootCause));
-        return this;
-    }
-}
diff --git a/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java b/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java
index 8d4f589744..cdc0263346 100644
--- a/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java
+++ b/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java
@@ -13,7 +13,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import junit.framework.AssertionFailedError;
+import org.opentest4j.AssertionFailedError;
 
 /**
  * Utilities to aid in making assertions about images using regular expressions.
diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
index f33894ff38..76b2b4f521 100644
--- a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
+++ b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
@@ -7,16 +7,14 @@ import java.awt.event.WindowEvent;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.security.GeneralSecurityException;
-import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.TimeZone;
@@ -30,11 +28,6 @@ import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.Statement;
 import org.openstreetmap.josm.JOSMFixture;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.actions.DeleteAction;
@@ -44,14 +37,12 @@ import org.openstreetmap.josm.data.SystemOfMeasurement;
 import org.openstreetmap.josm.data.UserIdentityManager;
 import org.openstreetmap.josm.data.Version;
 import org.openstreetmap.josm.data.osm.User;
-import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
 import org.openstreetmap.josm.data.preferences.JosmUrls;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
 import org.openstreetmap.josm.data.projection.Projections;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
-import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreferenceTestIT;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.OsmApi;
 import org.openstreetmap.josm.io.OsmApiInitializationException;
@@ -73,11 +64,10 @@ import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MemoryManagerTest;
-import org.openstreetmap.josm.tools.bugreport.ReportedException;
+import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.date.DateUtils;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import mockit.internal.state.SavePoint;
 
 /**
  * This class runs a test in an environment that resembles the one used by the JOSM main application.
@@ -86,16 +76,22 @@ import mockit.internal.state.SavePoint;
  * For example, invoking {@link #preferences()} gives you access to the (default) preferences.
  *
  * @author Michael Zangl
+ * @deprecated use the JUnit 5 annotations in {@link org.openstreetmap.josm.testutils.annotations} instead,
+ * e.g. {@link org.openstreetmap.josm.testutils.annotations.BasicPreferences} instead of {@link #preferences()}
  */
-public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback {
-    private int timeout = isDebugMode() ? -1 : 10 * 1000;
-    private TemporaryFolder josmHome;
+@Deprecated
+public class JOSMTestRules implements AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback {
+    private boolean useJosmHome;
+    private File josmHome;
+    private String originalJosmHome;
     private boolean usePreferences = false;
     private APIType useAPI = APIType.NONE;
     private String i18n = null;
     private TileSourceRule tileSourceRule;
     private String assumeRevisionString;
     private Version originalVersion;
+    private boolean revisionOverridden;
+    private String revisionBeforeOverride;
     private Runnable mapViewStateMockingRunnable;
     private Runnable navigableComponentMockingRunnable;
     private Runnable edtAssertionMockingRunnable;
@@ -109,19 +105,14 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
     private boolean territories;
     private boolean metric;
     private boolean main;
-    /**
-     * This boolean is only used to indicate if JUnit5 is used in a test. If it is,
-     * we must not call after in {@link JOSMTestRules.CreateJosmEnvironment#evaluate}.
-     * TODO: Remove JUnit4 as a whole sometime after 2021-01-01 (~6 month lead time for plugins)
-     */
-    private boolean junit5;
 
     /**
      * Disable the default timeout for this test. Use with care.
      * @return this instance, for easy chaining
+     * @deprecated no-op, timeouts are handled by {@link org.junit.jupiter.api.Timeout}
      */
+    @Deprecated
     public JOSMTestRules noTimeout() {
-        timeout = -1;
         return this;
     }
 
@@ -129,10 +120,10 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
      * Set a timeout for all tests in this class. Local method timeouts may only reduce this timeout.
      * @param millis The timeout duration in milliseconds.
      * @return this instance, for easy chaining
-     * @see org.junit.jupiter.api.Timeout
+     * @deprecated no-op, use {@link org.junit.jupiter.api.Timeout} instead
      */
+    @Deprecated
     public JOSMTestRules timeout(int millis) {
-        timeout = isDebugMode() ? -1 : millis;
         return this;
     }
 
@@ -153,7 +144,7 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
      * @return this instance, for easy chaining
      */
     private JOSMTestRules josmHome() {
-        josmHome = new TemporaryFolder();
+        useJosmHome = true;
         return this;
     }
 
@@ -410,15 +401,17 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
      * @return this instance, for easy chaining
      */
     public JOSMTestRules parameters() {
+        // Set up and tear down the environment immediately, as parameters are computed before anything else
         try {
-            apply(new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    // Do nothing. Hack needed because @Parameters are computed before anything else
-                }
-            }, Description.createSuiteDescription(ImageryPreferenceTestIT.class)).evaluate();
-        } catch (Throwable e) {
+            setUp();
+        } catch (ReflectiveOperationException e) {
             Logging.error(e);
+        } finally {
+            try {
+                tearDown();
+            } catch (ReflectiveOperationException e) {
+                Logging.error(e);
+            }
         }
         return this;
     }
@@ -431,65 +424,64 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
         }
     }
 
-    @Override
-    public Statement apply(Statement base, Description description) {
-        // First process any Override* annotations for per-test overrides.
-        // The following only work because "option" methods modify JOSMTestRules in-place
-        final OverrideAssumeRevision overrideAssumeRevision = description.getAnnotation(OverrideAssumeRevision.class);
-        if (overrideAssumeRevision != null) {
-            this.assumeRevision(overrideAssumeRevision.value());
-        }
-        final OverrideTimeout overrideTimeout = description.getAnnotation(OverrideTimeout.class);
-        if (overrideTimeout != null) {
-            this.timeout(overrideTimeout.value());
-        }
-        Statement statement = base;
-        // counter-intuitively, Statements which need to have their setup routines performed *after* another one need to
-        // be added into the chain *before* that one, so that it ends up on the "inside".
-        if (timeout > 0) {
-            // TODO: new DisableOnDebug(timeout)
-            statement = new FailOnTimeoutStatement(statement, timeout);
-        }
-
-        // this half of TileSourceRule's initialization must happen after josm is set up
+    /**
+     * Build the test environment. The mock tile server, if any, is started first so that it can be initializing in
+     * parallel with the rest of the setup; registering its layers has to wait until josm itself has been set up.
+     * @throws ReflectiveOperationException if a reflective access error occurs
+     */
+    private void setUp() throws ReflectiveOperationException {
         if (this.tileSourceRule != null) {
-            statement = this.tileSourceRule.applyRegisterLayers(statement, description);
+            this.tileSourceRule.startServer();
         }
-
-        statement = new CreateJosmEnvironment(statement);
-        if (josmHome != null) {
-            statement = josmHome.apply(statement, description);
+        before();
+        if (this.tileSourceRule != null) {
+            this.tileSourceRule.registerLayers();
         }
+    }
 
-        // run mock tile server as the outermost Statement (started first) so it can hopefully be initializing in
-        // parallel with other setup
-        if (this.tileSourceRule != null) {
-            statement = this.tileSourceRule.applyRunServer(statement, description);
+    /**
+     * Tear the test environment down again, in the reverse order of {@link #setUp}.
+     * @throws ReflectiveOperationException if a reflective access error occurs
+     */
+    private void tearDown() throws ReflectiveOperationException {
+        try {
+            if (this.tileSourceRule != null) {
+                this.tileSourceRule.unregisterLayers();
+            }
+            after();
+        } finally {
+            restoreJosmHome();
+            if (this.tileSourceRule != null) {
+                this.tileSourceRule.stopServer();
+            }
         }
-        return statement;
     }
 
     @Override
     public void beforeEach(ExtensionContext context) throws Exception {
-        this.junit5 = true;
-        Statement temporaryStatement = new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                // do nothing
-            }
-        };
-        try {
-            this.apply(temporaryStatement,
-                    Description.createTestDescription(this.getClass(), "JOSMTestRules JUnit5 Compatibility"))
-                    .evaluate();
-        } catch (Throwable e) {
-            throw new Exception(e);
-        }
+        // First process any Override* annotations for per-test overrides.
+        // The following only works because "option" methods modify JOSMTestRules in-place
+        context.getTestMethod()
+               .map(method -> method.getAnnotation(OverrideAssumeRevision.class))
+               .ifPresent(annotation -> {
+                   this.revisionOverridden = true;
+                   this.revisionBeforeOverride = this.assumeRevisionString;
+                   this.assumeRevision(annotation.value());
+               });
+        setUp();
     }
 
     @Override
     public void afterEach(ExtensionContext context) throws Exception {
-        after();
+        try {
+            tearDown();
+        } finally {
+            // the override only applies to the annotated test, not to the ones run after it with the same instance
+            if (this.revisionOverridden) {
+                this.assumeRevisionString = this.revisionBeforeOverride;
+                this.revisionOverridden = false;
+            }
+        }
     }
 
     @Override
@@ -504,26 +496,29 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
 
     /**
      * Set up before running a test
-     * @throws InitializationError If an error occurred while creating the required environment.
      * @throws ReflectiveOperationException if a reflective access error occurs
      */
-    protected void before() throws InitializationError, ReflectiveOperationException {
+    protected void before() throws ReflectiveOperationException {
         cleanUpFromJosmFixture();
 
         if (this.assumeRevisionString != null) {
-            this.originalVersion = Version.getInstance();
+            // as a static extension, before() runs again for each test without after() in between
+            if (this.originalVersion == null) {
+                this.originalVersion = Version.getInstance();
+            }
             final Version replacementVersion = new MockVersion(this.assumeRevisionString);
             TestUtils.setPrivateStaticField(Version.class, "instance", replacementVersion);
         }
 
         // Add JOSM home
-        if (josmHome != null) {
+        if (useJosmHome && josmHome == null) {
             try {
-                File home = josmHome.newFolder();
-                System.setProperty("josm.home", home.getAbsolutePath());
+                originalJosmHome = System.getProperty("josm.home");
+                josmHome = Files.createTempDirectory("josm-test-home").toFile();
+                System.setProperty("josm.home", josmHome.getAbsolutePath());
                 JosmBaseDirectories.getInstance().clearMemos();
             } catch (IOException e) {
-                throw new InitializationError(e);
+                throw new JosmRuntimeException(e);
             }
         }
 
@@ -603,7 +598,7 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
             try {
                 OsmApi.getOsmApi().initialize(null);
             } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
-                throw new InitializationError(e);
+                throw new JosmRuntimeException(e);
             }
         }
 
@@ -663,16 +658,11 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
      * Cleans the Layer manager and the SelectionEventManager.
      * You don't need to call this during tests, the test environment will do it for you.
      * @since 12070
+     * @deprecated use {@link JOSMFixture#cleanLayerEnvironment()} instead
      */
+    @Deprecated
     public static void cleanLayerEnvironment() {
-        // Get the instance before cleaning - this ensures that it is initialized.
-        SelectionEventManager eventManager = SelectionEventManager.getInstance();
-        MainApplication.getLayerManager().resetState();
-        try {
-            eventManager.resetState();
-        } catch (IllegalArgumentException e) {
-            Logging.trace(e);
-        }
+        JOSMFixture.cleanLayerEnvironment();
     }
 
     /**
@@ -703,8 +693,9 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
         ProjectionRegistry.clearProjectionChangeListeners();
         Preferences.main().resetToInitialState();
 
-        if (this.assumeRevisionString != null && this.originalVersion != null) {
+        if (this.originalVersion != null) {
             TestUtils.setPrivateStaticField(Version.class, "instance", this.originalVersion);
+            this.originalVersion = null;
         }
 
         Window[] windows = Window.getWindows();
@@ -726,99 +717,26 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
         JosmDefaults.DefaultsExtension.memoryCleanup();
     }
 
-    private final class CreateJosmEnvironment extends Statement {
-        private final Statement base;
-
-        private CreateJosmEnvironment(Statement base) {
-            this.base = base;
-        }
-
-        @Override
-        public void evaluate() throws Throwable {
-            // Needed since JMockit doesn't clean up JUnit4 vintage tests. We really shouldn't have to touch JMockit internal classes. :(
-            SavePoint savePoint = new SavePoint();
-            before();
-            try {
-                base.evaluate();
-            } finally {
-                if (!junit5) {
-                    after();
-                    savePoint.rollback();
-                }
-            }
-        }
-    }
-
-    enum APIType {
-        NONE, FAKE, DEV
-    }
-
     /**
-     * The junit timeout statement has problems when switching timezones. This one does not.
-     * @author Michael Zangl
+     * Delete the temporary josm home created by {@link #before}, if any, and switch {@code josm.home} back.
+     * Called by {@link #tearDown} even when {@link #after} fails part way, so that the next test does not inherit
+     * this one's home.
      */
-    private static class FailOnTimeoutStatement extends Statement {
-
-        private final int timeout;
-        private final Statement original;
-
-        FailOnTimeoutStatement(Statement original, int timeout) {
-            this.original = original;
-            this.timeout = timeout;
-        }
-
-        @Override
-        public void evaluate() throws Throwable {
-            TimeoutThread thread = new TimeoutThread(original);
-            thread.setDaemon(true);
-            thread.start();
-            thread.join(timeout);
-            thread.interrupt();
-            if (!thread.isDone) {
-                Throwable exception = thread.getExecutionException();
-                if (exception != null) {
-                    throw exception;
-                } else {
-                    if (Logging.isLoggingEnabled(Logging.LEVEL_DEBUG)) {
-                        // i.e. skip expensive formatting of stack trace if it won't be shown
-                        final StringWriter sw = new StringWriter();
-                        new ReportedException(exception).printReportThreadsTo(new PrintWriter(sw));
-                        Logging.debug("Thread state at timeout: {0}", sw);
-                    }
-                    throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout));
-                }
-            }
-        }
-    }
-
-    private static final class TimeoutThread extends Thread {
-        public boolean isDone;
-        private final Statement original;
-        private Throwable exceptionCaught;
-
-        private TimeoutThread(Statement original) {
-            super("Timeout runner");
-            this.original = original;
-        }
-
-        public Throwable getExecutionException() {
-            return exceptionCaught;
-        }
-
-        @Override
-        public void run() {
-            try {
-                original.evaluate();
-                isDone = true;
-            } catch (Throwable e) {
-                exceptionCaught = e;
+    private void restoreJosmHome() {
+        if (josmHome != null) {
+            Utils.deleteDirectory(josmHome);
+            josmHome = null;
+            if (originalJosmHome != null) {
+                System.setProperty("josm.home", originalJosmHome);
+            } else {
+                System.clearProperty("josm.home");
             }
+            JosmBaseDirectories.getInstance().clearMemos();
         }
     }
 
-    private boolean isDebugMode() {
-        return java.lang.management.ManagementFactory.getRuntimeMXBean().
-                getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
+    enum APIType {
+        NONE, FAKE, DEV
     }
 
     /**
@@ -838,8 +756,9 @@ public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCal
 
     /**
      * Override this test's timeout.
-     * @see JOSMTestRules#timeout(int)
+     * @deprecated no-op, use {@link org.junit.jupiter.api.Timeout} instead
      */
+    @Deprecated
     @Documented
     @Retention(RetentionPolicy.RUNTIME)
     @Target(ElementType.METHOD)
diff --git a/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java b/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
index c55dc43906..bccd887b21 100644
--- a/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
+++ b/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
@@ -17,23 +17,20 @@ import java.util.Objects;
 
 import javax.imageio.ImageIO;
 
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
 import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
 import org.openstreetmap.josm.tools.Logging;
 
+import com.github.tomakehurst.wiremock.WireMockServer;
 import com.github.tomakehurst.wiremock.client.MappingBuilder;
 import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
 import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
 
 /**
- * A JUnit rule, based on {@link WireMockRule} to provide a test with a simple mock tile server serving multiple tile
- * sources.
+ * A {@link WireMockServer} providing a test with a simple mock tile server serving multiple tile sources.
  */
-public class TileSourceRule extends WireMockRule {
+public class TileSourceRule extends WireMockServer {
     private static class ByteArrayWrapper {
         public final byte[] byteArray;
 
@@ -61,7 +58,7 @@ public class TileSourceRule extends WireMockRule {
 
         /**
          * @return a {@link MappingBuilder} representing the request matching properties of this tile source, suitable
-         * for passing to {@link WireMockRule#stubFor}.
+         * for passing to {@link WireMockServer#stubFor}.
          */
         public abstract MappingBuilder getMappingBuilder();
 
@@ -167,6 +164,11 @@ public class TileSourceRule extends WireMockRule {
     protected final boolean clearSlippyMapSources;
     protected final boolean registerInLayerList;
 
+    private List<SlippyMapBBoxChooser.TileSourceProvider> slippyMapProviders;
+    private SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider;
+    private List<ImageryInfo> originalImageryInfoList;
+    private boolean layersRegistered;
+
     /**
      * Construct a TileSourceRule for use with a JUnit test.
      *
@@ -184,8 +186,7 @@ public class TileSourceRule extends WireMockRule {
      *
      * The three boolean parameters control whether to perform various steps registering the tile sources with parts
      * of JOSM's internals as part of the setup process. It is advised to only enable any of these if it can be ensured
-     * that this rule will have its setup routine executed *after* the relevant parts of JOSM have been set up, e.g.
-     * when handled by {@link org.openstreetmap.josm.testutils.JOSMTestRules#fakeImagery}.
+     * that this rule will have its setup routine executed *after* the relevant parts of JOSM have been set up.
      *
      * @param clearLayerList whether to clear ImageryLayerInfo's layer list of any pre-existing entries
      * @param clearSlippyMapSources whether to clear SlippyMapBBoxChooser's stubborn fallback Mapnik TileSource
@@ -227,98 +228,87 @@ public class TileSourceRule extends WireMockRule {
     }
 
     /**
-     * A junit-rule {@code apply} method exposed separately to allow a chaining rule to put this much earlier in
-     * the test's initialization routine. The idea being to allow WireMock's web server to be starting up while other
-     * necessary initialization is taking place.
-     * See {@link org.junit.rules.TestRule#apply} for arguments.
-     * @param base The {@link Statement} to be modified
-     * @param description A {@link Description} of the test implemented in {@code base}
-     * @return a new statement, which may be the same as {@code base},
-     *         a wrapper around {@code base}, or a completely new Statement.
+     * Start the mock web server.
+     *
+     * Exposed separately from {@link #registerLayers} to allow a caller to put this much earlier in the test's
+     * initialization routine. The idea being to allow WireMock's web server to be starting up while other necessary
+     * initialization is taking place.
      */
-    public Statement applyRunServer(Statement base, Description description) {
-        return super.apply(new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                try {
-                    // a hack to circumvent a WireMock bug concerning delayed server startup. sending an early request
-                    // to the mock server seems to prompt it to start earlier (though this request itself is not
-                    // expected to succeed). see https://github.com/tomakehurst/wiremock/issues/97
-                    new java.net.URL(TileSourceRule.this.url("/_poke")).getContent();
-                } catch (IOException e) {
-                    Logging.trace(e);
-                }
-                base.evaluate();
-            }
-        }, description);
+    public void startServer() {
+        start();
+        WireMock.configureFor("localhost", port());
+        try {
+            // a hack to circumvent a WireMock bug concerning delayed server startup. sending an early request
+            // to the mock server seems to prompt it to start earlier (though this request itself is not
+            // expected to succeed). see https://github.com/tomakehurst/wiremock/issues/97
+            new java.net.URL(this.url("/_poke")).getContent();
+        } catch (IOException e) {
+            Logging.trace(e);
+        }
+    }
+
+    /**
+     * Stop the mock web server, if it is running.
+     */
+    public void stopServer() {
+        if (isRunning()) {
+            stop();
+        }
     }
 
     /**
-     * A junit-rule {@code apply} method exposed separately, containing initialization steps which can only be performed
-     * once more of josm's environment has been set up.
-     * See {@link org.junit.rules.TestRule#apply} for arguments.
-     * @param base The {@link Statement} to be modified
-     * @param description A {@link Description} of the test implemented in {@code base}
-     * @return a new statement, which may be the same as {@code base},
-     *         a wrapper around {@code base}, or a completely new Statement.
+     * Register the mock tile sources with JOSM's internals, as requested by the constructor's boolean flags.
+     *
+     * These are initialization steps which can only be performed once more of josm's environment has been set up,
+     * hence they are kept separate from {@link #startServer}. Must be paired with a call to {@link #unregisterLayers}.
+     * Calling it again before that is a no-op, so the state saved by the first call is kept.
      */
-    public Statement applyRegisterLayers(Statement base, Description description) {
-        if (this.registerInLayerList || this.clearLayerList) {
-            return new Statement() {
-                @Override
-                @SuppressWarnings("unchecked")
-                public void evaluate() throws Throwable {
-                    List<SlippyMapBBoxChooser.TileSourceProvider> slippyMapProviders = null;
-                    SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider = null;
-                    List<ImageryInfo> originalImageryInfoList = null;
-                    if (TileSourceRule.this.clearSlippyMapSources) {
-                        try {
-                            slippyMapProviders = (List<SlippyMapBBoxChooser.TileSourceProvider>) getPrivateStaticField(
-                                SlippyMapBBoxChooser.class,
-                                "providers"
-                            );
-                            // pop this off the beginning of the list, keep for later
-                            slippyMapDefaultProvider = slippyMapProviders.remove(0);
-                        } catch (ReflectiveOperationException e) {
-                            Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider");
-                        }
-                    }
-
-                    if (TileSourceRule.this.clearLayerList) {
-                        originalImageryInfoList = ImageryLayerInfo.instance.getLayers();
-                        ImageryLayerInfo.instance.clear();
-                    }
-                    if (TileSourceRule.this.registerInLayerList) {
-                        for (ConstSource source : TileSourceRule.this.sourcesList) {
-                            ImageryLayerInfo.addLayer(source.getImageryInfo(TileSourceRule.this.port()));
-                        }
-                    }
-
-                    try {
-                        base.evaluate();
-                    } finally {
-                        // clean up to original state
-                        if (slippyMapDefaultProvider != null && slippyMapProviders != null) {
-                            slippyMapProviders.add(0, slippyMapDefaultProvider);
-                        }
-                        if (originalImageryInfoList != null) {
-                            ImageryLayerInfo.instance.clear();
-                            ImageryLayerInfo.addLayers(originalImageryInfoList);
-                        }
-                    }
-                }
-            };
-        } else {
-            return base;
+    @SuppressWarnings("unchecked")
+    public void registerLayers() {
+        if (this.layersRegistered || !(this.registerInLayerList || this.clearLayerList)) {
+            return;
+        }
+        this.layersRegistered = true;
+        if (this.clearSlippyMapSources) {
+            try {
+                this.slippyMapProviders = (List<SlippyMapBBoxChooser.TileSourceProvider>) getPrivateStaticField(
+                    SlippyMapBBoxChooser.class,
+                    "providers"
+                );
+                // pop this off the beginning of the list, keep for later
+                this.slippyMapDefaultProvider = this.slippyMapProviders.remove(0);
+            } catch (ReflectiveOperationException e) {
+                Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider");
+            }
+        }
+
+        if (this.clearLayerList) {
+            // getLayers() hands out a live view of the internal list, so it has to be copied before clearing
+            this.originalImageryInfoList = List.copyOf(ImageryLayerInfo.instance.getLayers());
+            ImageryLayerInfo.instance.clear();
+        }
+        if (this.registerInLayerList) {
+            for (ConstSource source : this.sourcesList) {
+                ImageryLayerInfo.addLayer(source.getImageryInfo(this.port()));
+            }
         }
     }
 
     /**
-     * A standard implementation of apply which simply calls both sub- {@code apply} methods, {@link #applyRunServer}
-     * and {@link #applyRegisterLayers}. Called when used as a standard junit rule.
+     * Restore the state which was saved away by {@link #registerLayers}.
      */
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return applyRunServer(applyRegisterLayers(base, description), description);
+    public void unregisterLayers() {
+        this.layersRegistered = false;
+        if (this.slippyMapDefaultProvider != null && this.slippyMapProviders != null) {
+            this.slippyMapProviders.add(0, this.slippyMapDefaultProvider);
+        }
+        this.slippyMapProviders = null;
+        this.slippyMapDefaultProvider = null;
+
+        if (this.originalImageryInfoList != null) {
+            ImageryLayerInfo.instance.clear();
+            ImageryLayerInfo.addLayers(this.originalImageryInfoList);
+            this.originalImageryInfoList = null;
+        }
     }
 }
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/AssumeRevision.java b/test/unit/org/openstreetmap/josm/testutils/annotations/AssumeRevision.java
index ad074a93bb..391f46f265 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/AssumeRevision.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/AssumeRevision.java
@@ -15,11 +15,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.Version;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Override this test's assumed JOSM version (as reported by {@link Version}).
- * @see JOSMTestRules#assumeRevision(String)
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java
index b08c971e99..be15e7245f 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java
@@ -23,10 +23,9 @@ import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
 import org.openstreetmap.josm.data.preferences.JosmUrls;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.spi.preferences.Setting;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
- * Allow tests to use JOSM preferences (see {@link JOSMTestRules#preferences()}).
+ * Allow tests to use JOSM preferences.
  * This is often enough for basic tests. There are two modes:
  * <ul>
  *     <li>Between test classes (usually enough) if annotated at the class level ({@link ElementType#TYPE})</li>
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java b/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java
index bc15410274..10f1d2ce7b 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java
@@ -22,7 +22,6 @@ import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
 import org.openstreetmap.josm.gui.bbox.JosmMapViewer;
 import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.testutils.TileSourceRule;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -33,7 +32,6 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
  * Replace imagery sources with a default set of mock tile sources.
  * @author Taylor Smock
  * @since 18893
- * @see JOSMTestRules#fakeImagery()
  */
 @Inherited
 @Documented
@@ -75,9 +73,8 @@ public @interface FakeImagery {
         /**
          * Construct a FakeImageryWireMockExtension for use with a JUnit test.
          * <p>
-         * This is hidden for now, since all internal used {@link JOSMTestRules#fakeImagery()} instead of
-         * {@link JOSMTestRules#fakeImagery(TileSourceRule)}. Before making this public, we'll probably want to move
-         * {@link TileSourceRule.ConstSource} and it's subclasses around.
+         * This is hidden for now. Before making this public, we'll probably want to move
+         * {@link TileSourceRule.ConstSource} and its subclasses around.
          * <p>
          * The three boolean parameters control whether to perform various steps registering the tile sources with parts
          * of JOSM's internals as part of the setup process. It is advised to only enable any of these if it can be ensured
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/FullPreferences.java b/test/unit/org/openstreetmap/josm/testutils/annotations/FullPreferences.java
index 384befe9cd..7791ca0439 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/FullPreferences.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/FullPreferences.java
@@ -14,11 +14,10 @@ import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
 import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences.BasicPreferencesExtension;
 
 /**
- * Allow tests to use JOSM preferences with default values (see {@link JOSMTestRules#preferences()})
+ * Allow tests to use JOSM preferences with default values
  * @author Taylor Smock
  * @see BasicPreferences (often enough for simple tests).
  * @since 18037
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java b/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java
index 579e1fa861..9e9bddf8db 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java
@@ -15,13 +15,11 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.io.CertificateAmendment;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Set up the HTTPS certificates
  * @author Taylor Smock
  * @since 18893
- * @see JOSMTestRules#https()
  */
 @Inherited
 @Documented
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java b/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java
index ae515470e5..9fdeef0c56 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java
@@ -18,7 +18,6 @@ import org.openstreetmap.josm.tools.LanguageInfo;
 /**
  * Enables the i18n module for this test.
  * @author Taylor Smock
- * @see org.openstreetmap.josm.testutils.JOSMTestRules#i18n(String)
  * @since 17914
  */
 @Documented
@@ -37,7 +36,6 @@ public @interface I18n {
     /**
      * Enables the i18n module for this test.
      * @author Taylor Smock
-     * @see org.openstreetmap.josm.testutils.JOSMTestRules#i18n(String)
      */
     class I18nExtension implements AfterEachCallback, BeforeEachCallback {
         @Override
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/JosmDefaults.java b/test/unit/org/openstreetmap/josm/testutils/annotations/JosmDefaults.java
index 4146eb450f..18e5eaf88a 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/JosmDefaults.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/JosmDefaults.java
@@ -15,6 +15,7 @@ import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
+import org.openstreetmap.josm.JOSMFixture;
 import org.openstreetmap.josm.actions.DeleteAction;
 import org.openstreetmap.josm.command.DeleteCommand;
 import org.openstreetmap.josm.data.Preferences;
@@ -23,13 +24,12 @@ import org.openstreetmap.josm.data.osm.User;
 import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.OsmConnection;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MemoryManagerTest;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
- * Default actions taken by {@link JOSMTestRules}. Automatically registered.
+ * Default actions taken for every test. Automatically registered.
  * Functionality that this provides may be moved to other classes.
  */
 @Target({ElementType.TYPE, ElementType.METHOD})
@@ -37,7 +37,7 @@ import org.openstreetmap.josm.tools.Utils;
 @ExtendWith(JosmDefaults.DefaultsExtension.class)
 public @interface JosmDefaults {
     /**
-     * Default actions taken by {@link JOSMTestRules}. Automatically registered.
+     * Default actions taken for every test. Automatically registered.
      * Functionality that this provides may be moved to other classes.
      */
     class DefaultsExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback, AfterEachCallback {
@@ -94,7 +94,7 @@ public @interface JosmDefaults {
          */
         private static void cleanUpFromJosmFixture() {
             MemoryManagerTest.resetState(true);
-            JOSMTestRules.cleanLayerEnvironment();
+            JOSMFixture.cleanLayerEnvironment();
             Preferences.main().resetToInitialState();
             Preferences.main().enableSaveOnPut(false);
             memoryCleanup();
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/JosmHome.java b/test/unit/org/openstreetmap/josm/testutils/annotations/JosmHome.java
index 36493a6368..c065ffbbe7 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/JosmHome.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/JosmHome.java
@@ -21,10 +21,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
- * Use the JOSM home directory. See {@link JOSMTestRules}.
+ * Use the JOSM home directory.
  * Typically only used by {@link FullPreferences}.
  *
  * @author Taylor Smock
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java b/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java
index ea208fc022..c44c5b392a 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java
@@ -10,7 +10,7 @@ import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.JOSMFixture;
 
 /**
  * Clear the main {@link org.openstreetmap.josm.gui.layer.LayerManager} between tests.
@@ -30,7 +30,7 @@ public @interface LayerManager {
     class LayerManagerExtension implements BeforeEachCallback, AfterEachCallback {
         @Override
         public void afterEach(ExtensionContext context) {
-            JOSMTestRules.cleanLayerEnvironment();
+            JOSMFixture.cleanLayerEnvironment();
         }
 
         @Override
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java b/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java
index 7d696d29c5..70d2d14be8 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java
@@ -11,13 +11,11 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.gui.mappaint.ElemStyles;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Use map styles in tests.
  *
  * @author Taylor Smock
- * @see JOSMTestRules#mapStyles()
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java b/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java
index 563545f7c7..e6171f8f0a 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java
@@ -13,13 +13,11 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.data.SystemOfMeasurement;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Set up the system of measurement
  * @author Taylor Smock
  * @since 18893
- * @see JOSMTestRules#metricSystem()
  */
 @Inherited
 @Documented
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/MemoryManagerLeaks.java b/test/unit/org/openstreetmap/josm/testutils/annotations/MemoryManagerLeaks.java
index cddda690b0..a837ed3303 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/MemoryManagerLeaks.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/MemoryManagerLeaks.java
@@ -10,13 +10,11 @@ import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Allow the memory manager to contain items after execution of the test cases.
  * @author Taylor Smock
  * @since 18893
- * @see JOSMTestRules#memoryManagerLeaks()
  */
 @Inherited
 @Documented
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java b/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java
index b2df8a0e16..dd85bda419 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java
@@ -13,11 +13,11 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.platform.commons.support.ReflectionSupport;
-import org.junit.runners.model.InitializationError;
 import org.openstreetmap.josm.io.OsmApiInitializationException;
 import org.openstreetmap.josm.io.OsmTransferCanceledException;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.testutils.FakeOsmApi;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
 
 /**
  * Used for setting the desired OsmApi type
@@ -79,7 +79,7 @@ public @interface OsmApi {
                 try {
                     org.openstreetmap.josm.io.OsmApi.getOsmApi().initialize(null);
                 } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
-                    throw new InitializationError(e);
+                    throw new JosmRuntimeException(e);
                 }
             }
         }
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/Projection.java b/test/unit/org/openstreetmap/josm/testutils/annotations/Projection.java
index 20c24cfc92..587a792a74 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/Projection.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/Projection.java
@@ -16,13 +16,11 @@ import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.platform.commons.support.AnnotationSupport;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
 import org.openstreetmap.josm.data.projection.Projections;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Use projections in tests (Mercator).
  *
  * @author Taylor Smock
- * @see JOSMTestRules#projection()
  *
  */
 @Documented
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java b/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java
index 2722e0fa82..f06e0b6362 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java
@@ -12,14 +12,12 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
  * Use NAD projections in tests.
  *
  * @author Taylor Smock
- * @see JOSMTestRules#projectionNadGrids()
  *
  */
 @Documented
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java
index 4407cd69dd..7a89f31d60 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java
@@ -15,13 +15,11 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Use presets in tests.
  *
  * @author Taylor Smock
- * @see JOSMTestRules#presets()
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/Territories.java b/test/unit/org/openstreetmap/josm/testutils/annotations/Territories.java
index 7e6e93f912..642dd6d55a 100644
--- a/test/unit/org/openstreetmap/josm/testutils/annotations/Territories.java
+++ b/test/unit/org/openstreetmap/josm/testutils/annotations/Territories.java
@@ -14,12 +14,10 @@ import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.platform.commons.support.AnnotationSupport;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Use boundaries dataset in this test.
  *
- * @see JOSMTestRules#territories()
  * @author Taylor Smock
  * @since 18798
  */
diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java
index 929c6a02f3..730b59b4e5 100644
--- a/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java
+++ b/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java
@@ -42,7 +42,7 @@ import mockit.MockUp;
  * only the parts necessary for a particular case.
  *
  * The default {@link #getMockResultForMessage(Object)} will raise an
- * {@link junit.framework.AssertionFailedError} on an activation without a matching mapping entry or if
+ * {@link AssertionError} on an activation without a matching mapping entry or if
  * the mapped result value is invalid for the call.
  *
  * The public {@link #getMockResultMap()} method returns the modifiable result map to allow for situations
diff --git a/tools/ivy.xml b/tools/ivy.xml
index 8a5e203cdb..e23b561aa7 100644
--- a/tools/ivy.xml
+++ b/tools/ivy.xml
@@ -9,7 +9,6 @@
         <conf name="pmd" description="Everything needed for running PMD"/>
         <conf name="spotbugs" description="Everything needed for running SpotBugs"/>
         <conf name="errorprone" description="Everything needed for running error-prone"/>
-        <conf name="errorprone_javac" description="Everything needed for running javac from error-prone"/>
         <conf name="animal" description="Everything needed for running Animal Sniffer"/>
     </configurations>
     <dependencies>
