Index: /trunk/build.xml
===================================================================
--- /trunk/build.xml	(revision 18984)
+++ /trunk/build.xml	(revision 18985)
@@ -77,4 +77,6 @@
         <property name="dist-optimized.jar" location="${dist.dir}/josm-custom-optimized.jar"/>
         <property name="dist-sources.jar" location="${dist.dir}/josm-custom-sources.jar"/>
+        <!-- When incrementing the minimum Java version, don't forget to update the Java versions users will update to
+             (PlatformHook#getJavaUrl, PlatformHook#startupSanityChecks and PlatformHook#warnSoonToBeUnsupportedJava) -->
         <property name="java.lang.version" value="8" />
         <property name="test.headless" value="true" />
@@ -300,10 +302,4 @@
         <!-- Java 17 specific files (2021-09 LTS) -->
         <call-javac-compile-mrjar release="17"/>
-        <!-- Java 18 specific files -->
-        <call-javac-compile-mrjar release="18"/>
-        <!-- Java 19 specific files -->
-        <call-javac-compile-mrjar release="19"/>
-        <!-- Java 20 specific files -->
-        <call-javac-compile-mrjar release="20"/>
         <!-- Java 21 specific files (2023-09 LTS) -->
         <call-javac-compile-mrjar release="21"/>
Index: /trunk/native/linux/latest/DEBIAN/control
===================================================================
--- /trunk/native/linux/latest/DEBIAN/control	(revision 18984)
+++ /trunk/native/linux/latest/DEBIAN/control	(revision 18985)
@@ -6,5 +6,5 @@
 Priority: extra
 Architecture: all
-Depends: default-jre (>= 2:1.17) | java8-runtime,
+Depends: default-jre (>= 2:1.17) | java11-runtime,
          proj-data, fonts-noto, openjfx
 Description: Editor for OpenStreetMap (daily development snapshot)
Index: /trunk/native/linux/tested/DEBIAN/control
===================================================================
--- /trunk/native/linux/tested/DEBIAN/control	(revision 18984)
+++ /trunk/native/linux/tested/DEBIAN/control	(revision 18985)
@@ -6,5 +6,5 @@
 Priority: extra
 Architecture: all
-Depends: default-jre (>= 2:1.17) | java8-runtime,
+Depends: default-jre (>= 2:1.17) | java11-runtime,
          proj-data, fonts-noto, openjfx
 Conflicts: josm-plugins
Index: /trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 18985)
@@ -58,4 +58,5 @@
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JTextPane;
 import javax.swing.KeyStroke;
 import javax.swing.LookAndFeel;
@@ -130,4 +131,5 @@
 import org.openstreetmap.josm.gui.util.RedirectInputMap;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.widgets.TextContextualPopupMenu;
 import org.openstreetmap.josm.gui.widgets.UrlLabel;
 import org.openstreetmap.josm.io.CachedFile;
@@ -404,4 +406,38 @@
         askUpdate(tr("Outdated Java WebStart version"), tr("Update to OpenWebStart"), "askUpdateWebStart", /* ICON */"presets/transport/rocket", content, url);
         // CHECKSTYLE.ON: LineLength
+    }
+
+    /**
+     * Tells the user that a sanity check failed
+     * @param title The title of the message to show
+     * @param canContinue {@code true} if the failed sanity check(s) will not instantly kill JOSM when the user edits
+     * @param message The message parts to show the user (as a list)
+     */
+    public static void sanityCheckFailed(String title, boolean canContinue, String... message) {
+        final ExtendedDialog ed;
+        if (canContinue) {
+            ed = new ExtendedDialog(mainFrame, title, tr("Stop"), tr("Continue"));
+            ed.setButtonIcons("cancel", "ok");
+        } else {
+            ed = new ExtendedDialog(mainFrame, title, tr("Stop"));
+            ed.setButtonIcons("cancel");
+        }
+        ed.setDefaultButton(1).setCancelButton(1);
+        // Check if the dialog has not already been permanently hidden by user
+        if (!ed.toggleEnable("sanityCheckFailed").toggleCheckState() || !canContinue) {
+            final String content = Arrays.stream(message).collect(Collectors.joining("</li><li>",
+                    "<html><body><ul><li>", "</li></ul></body></html>"));
+            final JTextPane textField = new JTextPane();
+            textField.setContentType("text/html");
+            textField.setText(content);
+            TextContextualPopupMenu.enableMenuFor(textField, true);
+            ed.setMinimumSize(new Dimension(480, 300));
+            ed.setIcon(JOptionPane.WARNING_MESSAGE);
+            ed.setContent(textField);
+            ed.showDialog();
+        }
+        if (!canContinue || ed.getValue() <= 1) { // 0 == cancel (we want to stop) and 1 == stop
+            Lifecycle.exitJosm(true, -1);
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/MainInitialization.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainInitialization.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/gui/MainInitialization.java	(revision 18985)
@@ -72,5 +72,6 @@
             new InitializationTask(tr("Starting file watcher"), FileWatcher.getDefaultInstance()::start),
             new InitializationTask(tr("Executing platform startup hook"),
-                    () -> PlatformManager.getPlatform().startupHook(MainApplication::askUpdateJava, MainApplication::askMigrateWebStart)),
+                    () -> PlatformManager.getPlatform().startupHook(MainApplication::askUpdateJava,
+                            MainApplication::askMigrateWebStart, MainApplication::sanityCheckFailed)),
             new InitializationTask(tr("Building main menu"), application::initializeMainWindow),
             new InitializationTask(tr("Updating user interface"), () -> {
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHook.java	(revision 18985)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.GraphicsEnvironment;
@@ -9,4 +11,5 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
 import java.nio.charset.StandardCharsets;
 import java.security.KeyStoreException;
@@ -15,4 +18,5 @@
 import java.security.cert.X509Certificate;
 import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -87,8 +91,9 @@
       * @param javaCallback Java expiration callback, providing GUI feedback
       * @param webStartCallback WebStart migration callback, providing GUI feedback
-      * @since 17679 (signature)
+      * @since 18985
       */
-    default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
-        // Do nothing
+    default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback,
+            SanityCheckCallback sanityCheckCallback) {
+        startupSanityChecks(sanityCheckCallback);
     }
 
@@ -288,4 +293,18 @@
 
     /**
+     * Inform the user that a sanity check or checks failed
+     */
+    @FunctionalInterface
+    interface SanityCheckCallback {
+        /**
+         * Tells the user that a sanity check failed
+         * @param title The title of the message to show
+         * @param canContinue {@code true} if the failed sanity check(s) will not instantly kill JOSM when the user edits
+         * @param message The message parts to show the user (as a list)
+         */
+        void sanityCheckFailed(String title, boolean canContinue, String... message);
+    }
+
+    /**
      * Checks if the running version of Java has expired, proposes to user to update it if needed.
      * @param callback Java expiration callback
@@ -332,5 +351,5 @@
      */
     default String getJavaUrl() {
-        StringBuilder defaultDownloadUrl = new StringBuilder("https://www.azul.com/downloads/?version=java-17-lts");
+        StringBuilder defaultDownloadUrl = new StringBuilder("https://www.azul.com/downloads/?version=java-21-lts");
         if (PlatformManager.isPlatformWindows()) {
             defaultDownloadUrl.append("&os=windows");
@@ -370,4 +389,51 @@
     }
 
+    default void startupSanityChecks(SanityCheckCallback sanityCheckCallback) {
+        final String arch = System.getProperty("os.arch");
+        final List<String> messages = new ArrayList<>();
+        final String jvmArch = System.getProperty("sun.arch.data.model");
+        boolean canContinue = true;
+        if (Utils.getJavaVersion() < 11) {
+            canContinue = false;
+            messages.add(tr("You must update Java to Java {0} or later in order to run this version of JOSM", 17));
+            // Reset webstart/java update prompts
+            Config.getPref().put("askUpdateWebStart", null);
+            Config.getPref().put("askUpdateJava" + Utils.getJavaLatestVersion(), null);
+            Config.getPref().put("askUpdateJavalatest", null);
+        }
+        if (!"x86".equals(arch) && "32".equals(jvmArch)) {
+            messages.add(tr("Please use a 64 bit version of Java -- this will avoid out of memory errors"));
+        }
+        // Note: these might be able to be removed with the appropriate module-info.java settings.
+        final String[] expectedJvmArguments = {
+                "--add-exports=java.base/sun.security.action=ALL-UNNAMED",
+                "--add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED",
+                "--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED"
+        };
+        final List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
+        final StringBuilder missingArguments = new StringBuilder();
+        for (String arg : expectedJvmArguments) {
+            if (!vmArguments.contains(arg)) {
+                if (missingArguments.length() > 0) {
+                    missingArguments.append("<br>");
+                }
+                missingArguments.append(arg);
+            }
+        }
+        if (missingArguments.length() > 0) {
+            final String args = missingArguments.toString();
+            messages.add(tr("Missing JVM Arguments:<br>{0}", args));
+        }
+        if (!messages.isEmpty()) {
+            if (canContinue) {
+                sanityCheckCallback.sanityCheckFailed(tr("JOSM may work improperly"), true,
+                        messages.toArray(new String[0]));
+            } else {
+                sanityCheckCallback.sanityCheckFailed(tr("JOSM will be unable to work properly and will exit"), false,
+                        messages.toArray(new String[0]));
+            }
+        }
+    }
+
     /**
      * Called when interfacing with native OS functions. Currently only used with macOS.
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookOsx.java	(revision 18985)
@@ -69,5 +69,6 @@
 
     @Override
-    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
+    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback,
+            SanityCheckCallback sanityCheckCallback) {
         // Here we register callbacks for the menu entries in the system menu and file opening through double-click
         // https://openjdk.java.net/jeps/272
@@ -107,4 +108,5 @@
         checkExpiredJava(javaCallback);
         checkWebStartMigration(webStartCallback);
+        PlatformHook.super.startupHook(javaCallback, webStartCallback, sanityCheckCallback);
     }
 
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java	(revision 18985)
@@ -64,6 +64,8 @@
 
     @Override
-    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
+    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback,
+            SanityCheckCallback sanityCheckCallback) {
         checkWebStartMigration(webStartCallback);
+        PlatformHook.super.startupHook(javaCallback, webStartCallback, sanityCheckCallback);
     }
 
Index: /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java	(revision 18984)
+++ /trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java	(revision 18985)
@@ -154,8 +154,10 @@
 
     @Override
-    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
+    public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback,
+            SanityCheckCallback sanityCheckCallback) {
         warnSoonToBeUnsupportedJava(javaCallback);
         checkExpiredJava(javaCallback);
         checkWebStartMigration(webStartCallback);
+        PlatformHook.super.startupHook(javaCallback, webStartCallback, sanityCheckCallback);
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java	(revision 18984)
+++ /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java	(revision 18985)
@@ -35,5 +35,6 @@
     @Test
     void testStartupHook() {
-        hook.startupHook((a, b, c, d) -> System.out.println("java callback"), u -> System.out.println("webstart callback"));
+        hook.startupHook((a, b, c, d) -> System.out.println("java callback"), u -> System.out.println("webstart callback"),
+                (a, b, c) -> System.out.println("sanity check callback"));
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java	(revision 18984)
+++ /trunk/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java	(revision 18985)
@@ -47,5 +47,6 @@
         final PlatformHook.JavaExpirationCallback javaCallback = (a, b, c, d) -> System.out.println("java callback");
         final PlatformHook.WebStartMigrationCallback webstartCallback = u -> System.out.println("webstart callback");
-        assertDoesNotThrow(() -> hook.startupHook(javaCallback, webstartCallback));
+        final PlatformHook.SanityCheckCallback sanityCheckCallback = (a, b, c) -> System.out.println("sanity check callback");
+        assertDoesNotThrow(() -> hook.startupHook(javaCallback, webstartCallback, sanityCheckCallback));
     }
 
