Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 19195)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 19196)
@@ -14,4 +14,5 @@
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -27,4 +28,5 @@
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.OsmApiException;
 import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
@@ -47,4 +49,6 @@
     /** preference to define OSM download timeout in seconds */
     public static final IntegerProperty OSM_DOWNLOAD_TIMEOUT = new IntegerProperty("remotecontrol.osm.download.timeout", 5*60);
+    /** A lock to ensure that messages are shown in the order the commands were sent from the server (see #23821) */
+    private static final ReentrantLock MESSAGE_LOCK = new ReentrantLock(true);
 
     protected static final Pattern SPLITTER_COMMA = Pattern.compile(",\\s*");
@@ -98,5 +102,5 @@
      * Handle a specific command sent as remote control.
      * Any time-consuming operation must be performed asynchronously to avoid delaying the HTTP response.
-     *
+     * <p>
      * This method of the subclass will do the real work.
      *
@@ -109,5 +113,5 @@
      * Get a specific message to ask the user for permission for the operation
      * requested via remote control.
-     *
+     * <p>
      * This message will be displayed to the user if the preference
      * remotecontrol.always-confirm is true.
@@ -121,5 +125,5 @@
      * preference to individually allow the requested operation and an error
      * message to be displayed when a disabled operation is requested.
-     *
+     * <p>
      * Default is not to check any special preference. Override this in a
      * subclass to define permission preference and error message.
@@ -201,16 +205,31 @@
          * If yes, display specific confirmation message.
          */
-        if (GLOBAL_CONFIRMATION.get()) {
+        if (Boolean.TRUE.equals(GLOBAL_CONFIRMATION.get())) {
             // Ensure dialog box does not exceed main window size
-            int maxWidth = (int) Math.max(200, MainApplication.getMainFrame().getWidth() * 0.6);
-            String message = "<html><div>" + getPermissionMessage() +
+            final int maxWidth = (int) Math.max(200, MainApplication.getMainFrame().getWidth() * 0.6);
+            final String message = "<html><div>" + getPermissionMessage() +
                     "<br/>" + tr("Do you want to allow this?") + "</div></html>";
-            JLabel label = new JLabel(message);
-            if (label.getPreferredSize().width > maxWidth) {
-                label.setText(message.replaceFirst("<div>", "<div style=\"width:" + maxWidth + "px;\">"));
+            final Object[] choices = {tr("Yes, always"), tr("Yes, once"), tr("No")};
+            final int choice;
+            // The ordering of the requests can be important, so we use a fair lock to ensure ordering
+            // Note that the EDT will be more than happy to show multiple dialogs without blocking.
+            try {
+                MESSAGE_LOCK.lock();
+                final Integer tChoice = GuiHelper.runInEDTAndWaitAndReturn(() -> {
+                    final JLabel label = new JLabel(message);
+                    if (label.getPreferredSize().width > maxWidth) {
+                        label.setText(message.replaceFirst("<div>", "<div style=\"width:" + maxWidth + "px;\">"));
+                    }
+                    return JOptionPane.showOptionDialog(MainApplication.getMainFrame(), label, tr("Confirm Remote Control action"),
+                            JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[1]);
+                });
+                if (tChoice == null) {
+                    // I have no clue how this would ever happen, but just in case.
+                    throw new RequestHandlerForbiddenException(MessageFormat.format("RemoteControl: ''{0}'' forbidden due to NPE", myCommand));
+                }
+                choice = tChoice;
+            } finally {
+                MESSAGE_LOCK.unlock();
             }
-            Object[] choices = {tr("Yes, always"), tr("Yes, once"), tr("No")};
-            int choice = JOptionPane.showOptionDialog(MainApplication.getMainFrame(), label, tr("Confirm Remote Control action"),
-                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[1]);
             if (choice != JOptionPane.YES_OPTION && choice != JOptionPane.NO_OPTION) { // Yes/no refer to always/once
                 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by user''s choice", myCommand);
@@ -240,5 +259,5 @@
      * Parse the request parameters as key=value pairs.
      * The result will be stored in {@code this.args}.
-     *
+     * <p>
      * Can be overridden by subclass.
      * @throws URISyntaxException if request URL is invalid
