Index: trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 12789)
+++ trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 12790)
@@ -149,4 +149,5 @@
 import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
 import org.openstreetmap.josm.tools.bugreport.BugReportQueue;
+import org.openstreetmap.josm.tools.bugreport.BugReportSender;
 import org.xml.sax.SAXException;
 
@@ -778,4 +779,5 @@
         if (!GraphicsEnvironment.isHeadless()) {
             BugReportQueue.getInstance().setBugReportHandler(BugReportDialog::showFor);
+            BugReportSender.setBugReportSendingHandler(BugReportDialog.bugReportSendingHandler);
         }
 
Index: trunk/src/org/openstreetmap/josm/gui/bugreport/BugReportDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/bugreport/BugReportDialog.java	(revision 12789)
+++ trunk/src/org/openstreetmap/josm/gui/bugreport/BugReportDialog.java	(revision 12790)
@@ -19,4 +19,5 @@
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 
@@ -32,7 +33,9 @@
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.InputMapUtils;
+import org.openstreetmap.josm.tools.OpenBrowser;
 import org.openstreetmap.josm.tools.bugreport.BugReport;
 import org.openstreetmap.josm.tools.bugreport.BugReportQueue.SuppressionMode;
 import org.openstreetmap.josm.tools.bugreport.BugReportSender;
+import org.openstreetmap.josm.tools.bugreport.BugReportSender.BugReportSendingHandler;
 import org.openstreetmap.josm.tools.bugreport.ReportedException;
 
@@ -52,4 +55,31 @@
     private JCheckBox cbSuppressSingle;
     private JCheckBox cbSuppressAll;
+
+    /**
+     * Default bug report callback that opens the bug report form in user browser
+     * and displays a dialog in case of error.
+     * @since 12790
+     */
+    public static final BugReportSendingHandler bugReportSendingHandler = new BugReportSendingHandler() {
+        @Override
+        public String sendingBugReport(String bugUrl, String statusText) {
+            return OpenBrowser.displayUrl(bugUrl);
+        }
+
+        @Override
+        public void failed(String errorMessage, String statusText) {
+            SwingUtilities.invokeLater(() -> {
+                JPanel errorPanel = new JPanel(new GridBagLayout());
+                errorPanel.add(new JMultilineLabel(
+                        tr("Opening the bug report failed. Please report manually using this website:")),
+                        GBC.eol().fill(GridBagConstraints.HORIZONTAL));
+                errorPanel.add(new UrlLabel(Main.getJOSMWebsite() + "/newticket", 2), GBC.eop().insets(8, 0, 0, 0));
+                errorPanel.add(new DebugTextDisplay(statusText));
+
+                JOptionPane.showMessageDialog(Main.parent, errorPanel, tr("You have encountered a bug in JOSM"),
+                        JOptionPane.ERROR_MESSAGE);
+            });
+        }
+    };
 
     /**
Index: trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java	(revision 12789)
+++ trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java	(revision 12790)
@@ -4,4 +4,5 @@
 import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Predicate;
@@ -18,4 +19,8 @@
     private static final BugReportQueue INSTANCE = new BugReportQueue();
 
+    /**
+     * The fallback bug report handler if none is set. Prints the stacktrace on standard output.
+     * @since 12770
+     */
     public static final BugReportHandler FALLBACK_BUGREPORT_HANDLER = (e, index) -> {
         e.printStackTrace();
@@ -142,6 +147,11 @@
     }
 
+    /**
+     * Sets the {@link BugReportHandler} for this queue.
+     * @param bugReportHandler the handler in charge of displaying the bug report. Must not be null
+     * @since 12770
+     */
     public void setBugReportHandler(BugReportHandler bugReportHandler) {
-        this.bugReportHandler = bugReportHandler;
+        this.bugReportHandler = Objects.requireNonNull(bugReportHandler, "bugReportHandler");
     }
 
Index: trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportSender.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportSender.java	(revision 12789)
+++ trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportSender.java	(revision 12790)
@@ -2,8 +2,4 @@
 package org.openstreetmap.josm.tools.bugreport;
 
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
 import java.io.IOException;
 import java.io.InputStream;
@@ -12,8 +8,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
+import java.util.Objects;
 
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.xpath.XPath;
@@ -23,8 +17,4 @@
 
 import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.bugreport.DebugTextDisplay;
-import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
-import org.openstreetmap.josm.gui.widgets.UrlLabel;
-import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.HttpClient;
 import org.openstreetmap.josm.tools.HttpClient.Response;
@@ -45,4 +35,43 @@
 public class BugReportSender extends Thread {
 
+    /**
+     * Called during bug submission to JOSM bugtracker. Completes the bug report submission and handles errors.
+     * @since 12790
+     */
+    public interface BugReportSendingHandler {
+        /**
+         * Called when a bug is sent to JOSM bugtracker.
+         * @param bugUrl URL to visit to effectively submit the bug report to JOSM website
+         * @param statusText the status text being sent
+         * @return <code>null</code> for success or a string in case of an error
+         */
+        String sendingBugReport(String bugUrl, String statusText);
+
+        /**
+         * Called when a bug failed to be sent to JOSM bugtracker.
+         * @param errorMessage the error message
+         * @param statusText the status text being sent
+         */
+        void failed(String errorMessage, String statusText);
+    }
+
+    /**
+     * The fallback bug report sending handler if none is set.
+     * @since xxx
+     */
+    public static final BugReportSendingHandler FALLBACK_BUGREPORT_SENDING_HANDLER = new BugReportSendingHandler() {
+        @Override
+        public String sendingBugReport(String bugUrl, String statusText) {
+            return OpenBrowser.displayUrl(bugUrl);
+        }
+
+        @Override
+        public void failed(String errorMessage, String statusText) {
+            Logging.error("Unable to send bug report: {0}\n{1}", errorMessage, statusText);
+        }
+    };
+
+    private static BugReportSendingHandler handler = FALLBACK_BUGREPORT_SENDING_HANDLER;
+
     private final String statusText;
     private String errorMessage;
@@ -62,14 +91,16 @@
             // first, send the debug text using post.
             String debugTextPasteId = pasteDebugText();
+            String bugUrl = getJOSMTicketURL() + "?pdata_stored=" + debugTextPasteId;
 
-            // then open a browser to display the pasted text.
-            String openBrowserError = OpenBrowser.displayUrl(getJOSMTicketURL() + "?pdata_stored=" + debugTextPasteId);
-            if (openBrowserError != null) {
-                Logging.warn(openBrowserError);
-                failed(openBrowserError);
+            // then notify handler
+            errorMessage = handler.sendingBugReport(bugUrl, statusText);
+            if (errorMessage != null) {
+                Logging.warn(errorMessage);
+                handler.failed(errorMessage, statusText);
             }
         } catch (BugReportSenderException e) {
             Logging.warn(e);
-            failed(e.getMessage());
+            errorMessage = e.getMessage();
+            handler.failed(errorMessage, statusText);
         }
     }
@@ -128,19 +159,4 @@
     }
 
-    private void failed(String string) {
-        errorMessage = string;
-        SwingUtilities.invokeLater(() -> {
-            JPanel errorPanel = new JPanel(new GridBagLayout());
-            errorPanel.add(new JMultilineLabel(
-                    tr("Opening the bug report failed. Please report manually using this website:")),
-                    GBC.eol().fill(GridBagConstraints.HORIZONTAL));
-            errorPanel.add(new UrlLabel(Main.getJOSMWebsite() + "/newticket", 2), GBC.eop().insets(8, 0, 0, 0));
-            errorPanel.add(new DebugTextDisplay(statusText));
-
-            JOptionPane.showMessageDialog(Main.parent, errorPanel, tr("You have encountered a bug in JOSM"),
-                    JOptionPane.ERROR_MESSAGE);
-        });
-    }
-
     /**
      * Returns the error message that could have occured during bug sending.
@@ -171,3 +187,12 @@
         return sender;
     }
+
+    /**
+     * Sets the {@link BugReportSendingHandler} for bug report sender.
+     * @param bugReportSendingHandler the handler in charge of completing the bug report submission and handle errors. Must not be null
+     * @since 12790
+     */
+    public static void setBugReportSendingHandler(BugReportSendingHandler bugReportSendingHandler) {
+        handler = Objects.requireNonNull(bugReportSendingHandler, "bugReportSendingHandler");
+    }
 }
Index: trunk/test/unit/org/openstreetmap/josm/gui/bugreport/BugReportSettingsPanelTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/bugreport/BugReportSettingsPanelTest.java	(revision 12790)
+++ trunk/test/unit/org/openstreetmap/josm/gui/bugreport/BugReportSettingsPanelTest.java	(revision 12790)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.bugreport;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.bugreport.BugReport;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Tests the {@link BugReportSettingsPanel} class.
+ */
+public class BugReportSettingsPanelTest {
+
+    /**
+     * Setup test
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Test {@link BugReportSettingsPanel}
+     */
+    @Test
+    public void testBugReportSettingsPanel() {
+        assertNotNull(new BugReportSettingsPanel(new BugReport(BugReport.intercept(new Exception()))));
+    }
+}
Index: trunk/test/unit/org/openstreetmap/josm/gui/bugreport/DebugTextDisplayTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/bugreport/DebugTextDisplayTest.java	(revision 12790)
+++ trunk/test/unit/org/openstreetmap/josm/gui/bugreport/DebugTextDisplayTest.java	(revision 12790)
@@ -0,0 +1,40 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.bugreport;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit test of {@link DebugTextDisplay} class.
+ */
+public class DebugTextDisplayTest {
+    /**
+     * Setup test
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences();
+
+    /**
+     * Test {@link DebugTextDisplay#getCodeText}
+     */
+    @Test
+    public void testGetCodeText() {
+        assertEquals("test", new DebugTextDisplay("test").getCodeText());
+    }
+
+    /**
+     * Test {@link DebugTextDisplay#copyToClipboard}
+     */
+    @Test
+    public void testCopyToClipboard() {
+        new DebugTextDisplay("copy").copyToClipboard();
+        assertEquals(String.format("{{{%ncopy%n}}}"), ClipboardUtils.getClipboardStringContent());
+    }
+}
Index: trunk/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportSettingsPanelTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportSettingsPanelTest.java	(revision 12789)
+++ 	(revision )
@@ -1,32 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.tools.bugreport;
-
-import static org.junit.Assert.assertNotNull;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.openstreetmap.josm.gui.bugreport.BugReportSettingsPanel;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Tests the {@link BugReportSettingsPanel} class.
- */
-public class BugReportSettingsPanelTest {
-
-    /**
-     * Setup test
-     */
-    @Rule
-    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
-
-    /**
-     * Test {@link BugReportSettingsPanel}
-     */
-    @Test
-    public void testBugReportSettingsPanel() {
-        assertNotNull(new BugReportSettingsPanel(new BugReport(BugReport.intercept(new Exception()))));
-    }
-}
Index: trunk/test/unit/org/openstreetmap/josm/tools/bugreport/DebugTextDisplayTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/tools/bugreport/DebugTextDisplayTest.java	(revision 12789)
+++ 	(revision )
@@ -1,41 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.tools.bugreport;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.openstreetmap.josm.gui.bugreport.DebugTextDisplay;
-import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Unit test of {@link DebugTextDisplay} class.
- */
-public class DebugTextDisplayTest {
-    /**
-     * Setup test
-     */
-    @Rule
-    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules().preferences();
-
-    /**
-     * Test {@link DebugTextDisplay#getCodeText}
-     */
-    @Test
-    public void testGetCodeText() {
-        assertEquals("test", new DebugTextDisplay("test").getCodeText());
-    }
-
-    /**
-     * Test {@link DebugTextDisplay#copyToClipboard}
-     */
-    @Test
-    public void testCopyToClipboard() {
-        new DebugTextDisplay("copy").copyToClipboard();
-        assertEquals(String.format("{{{%ncopy%n}}}"), ClipboardUtils.getClipboardStringContent());
-    }
-}
