Index: trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 17237)
+++ trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java	(revision 17238)
@@ -93,5 +93,5 @@
         hcbUploadComment.setToolTipText(tr("Enter an upload comment"));
         hcbUploadComment.setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);
-        populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<String>());
+        populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>());
         CommentModelListener commentModelListener = new CommentModelListener(hcbUploadComment, changesetCommentModel);
         hcbUploadComment.getEditor().addActionListener(commentModelListener);
@@ -172,5 +172,5 @@
      */
     protected void refreshHistoryComboBoxes() {
-        populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<String>());
+        populateHistoryComboBox(hcbUploadComment, HISTORY_KEY, new LinkedList<>());
         populateHistoryComboBox(hcbUploadSource, SOURCE_HISTORY_KEY, getDefaultSources());
     }
@@ -195,4 +195,13 @@
     public static List<String> getDefaultSources() {
         return Arrays.asList("knowledge", "survey", "Bing");
+    }
+
+    /**
+     * Returns the list of {@link UploadTextComponentValidator} defined by this panel.
+     * @return the list of {@code UploadTextComponentValidator} defined by this panel.
+     * @since 17238
+     */
+    protected List<UploadTextComponentValidator> getUploadTextValidators() {
+        return Arrays.asList(areaValidator, uploadCommentValidator, uploadSourceValidator);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 17237)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 17238)
@@ -233,4 +233,8 @@
         pnlBasicUploadSettings.setUploadTagDownFocusTraversalHandlers(e -> btnUpload.requestFocusInWindow());
 
+        // Enable/disable the upload button if at least an upload validator rejects upload
+        pnlBasicUploadSettings.getUploadTextValidators().forEach(v -> v.addChangeListener(e -> btnUpload.setEnabled(
+                pnlBasicUploadSettings.getUploadTextValidators().stream().noneMatch(UploadTextComponentValidator::isUploadRejected))));
+
         setMinimumSize(new Dimension(600, 350));
 
Index: trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java	(revision 17237)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java	(revision 17238)
@@ -22,4 +22,5 @@
 abstract class UploadTextComponentValidator extends AbstractTextComponentValidator {
     private final JLabel feedback;
+    protected boolean uploadRejected;
 
     UploadTextComponentValidator(JTextComponent tc, JLabel feedback) {
@@ -60,4 +61,13 @@
 
     /**
+     * Determines if the input validator considers the violation bad enough to reject upload.
+     * @return {@code true} if the input validator considers the violation bad enough to reject upload
+     * @since 17238
+     */
+    public final boolean isUploadRejected() {
+        return uploadRejected;
+    }
+
+    /**
      * Validator for the changeset {@code comment} tag
      */
@@ -85,5 +95,6 @@
                 String rejection = UploadDialog.UploadAction.validateUploadTag(uploadComment, "upload.comment",
                         Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
-                if (rejection != null) {
+                uploadRejected = rejection != null;
+                if (uploadRejected) {
                     feedbackWarning(tr("Your upload comment is <i>rejected</i>.") + "<br />" + rejection);
                 } else {
@@ -120,5 +131,6 @@
                 final String rejection = UploadDialog.UploadAction.validateUploadTag(
                         uploadSource, "upload.source", Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
-                if (rejection != null) {
+                uploadRejected = rejection != null;
+                if (uploadRejected) {
                     feedbackWarning(tr("Your changeset source is <i>rejected</i>.") + "<br />" + rejection);
                 } else {
Index: trunk/src/org/openstreetmap/josm/gui/widgets/AbstractTextComponentValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/widgets/AbstractTextComponentValidator.java	(revision 17237)
+++ trunk/src/org/openstreetmap/josm/gui/widgets/AbstractTextComponentValidator.java	(revision 17238)
@@ -21,4 +21,5 @@
 
 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
+import org.openstreetmap.josm.gui.util.ChangeNotifier;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
@@ -35,5 +36,6 @@
  * @since 2688
  */
-public abstract class AbstractTextComponentValidator implements ActionListener, FocusListener, DocumentListener, PropertyChangeListener {
+public abstract class AbstractTextComponentValidator extends ChangeNotifier
+    implements ActionListener, FocusListener, DocumentListener, PropertyChangeListener {
 
     protected static final Color ERROR_COLOR = new NamedColorProperty(marktr("Input validation: error"), Color.RED).get();
@@ -63,9 +65,5 @@
         if (hasChanged(msg, Status.INVALID)) {
             // only provide feedback if the validity has changed. This avoids unnecessary UI updates.
-            tc.setBorder(ERROR_BORDER);
-            tc.setBackground(ERROR_BACKGROUND);
-            tc.setToolTipText(msg);
-            this.status = Status.INVALID;
-            this.msg = msg;
+            feedback(ERROR_BORDER, ERROR_BACKGROUND, msg, Status.INVALID, msg);
         }
     }
@@ -74,9 +72,5 @@
         if (hasChanged(msg, Status.WARNING)) {
             // only provide feedback if the validity has changed. This avoids unnecessary UI updates.
-            tc.setBorder(WARNING_BORDER);
-            tc.setBackground(WARNING_BACKGROUND);
-            tc.setToolTipText(msg);
-            this.status = Status.WARNING;
-            this.msg = msg;
+            feedback(WARNING_BORDER, WARNING_BACKGROUND, msg, Status.WARNING, msg);
         }
     }
@@ -89,9 +83,9 @@
         if (hasChanged(msg, Status.VALID)) {
             // only provide feedback if the validity has changed. This avoids unnecessary UI updates.
-            tc.setBorder(msg == null ? UIManager.getBorder("TextField.border") : VALID_BORDER);
-            tc.setBackground(UIManager.getColor("TextField.background"));
-            tc.setToolTipText(msg == null ? "" : msg);
-            this.status = Status.VALID;
-            this.msg = msg;
+            feedback(msg == null ? UIManager.getBorder("TextField.border") : VALID_BORDER,
+                UIManager.getColor("TextField.background"),
+                msg == null ? "" : msg,
+                Status.VALID,
+                msg);
         }
     }
@@ -99,4 +93,13 @@
     private boolean hasChanged(String msg, Status status) {
         return !(Objects.equals(status, this.status) && Objects.equals(msg, this.msg));
+    }
+
+    private void feedback(Border border, Color background, String tooltip, Status status, String msg) {
+        tc.setBorder(border);
+        tc.setBackground(background);
+        tc.setToolTipText(tooltip);
+        this.status = status;
+        this.msg = msg;
+        fireStateChanged();
     }
 
@@ -171,8 +174,8 @@
     /* -------------------------------------------------------------------------------- */
     @Override
-    public void focusGained(FocusEvent arg0) {}
-
-    @Override
-    public void focusLost(FocusEvent arg0) {
+    public void focusGained(FocusEvent e) {}
+
+    @Override
+    public void focusLost(FocusEvent e) {
         validate();
     }
@@ -182,5 +185,5 @@
     /* -------------------------------------------------------------------------------- */
     @Override
-    public void actionPerformed(ActionEvent arg0) {
+    public void actionPerformed(ActionEvent e) {
         validate();
     }
@@ -190,15 +193,15 @@
     /* -------------------------------------------------------------------------------- */
     @Override
-    public void changedUpdate(DocumentEvent arg0) {
-        validate();
-    }
-
-    @Override
-    public void insertUpdate(DocumentEvent arg0) {
-        validate();
-    }
-
-    @Override
-    public void removeUpdate(DocumentEvent arg0) {
+    public void changedUpdate(DocumentEvent e) {
+        validate();
+    }
+
+    @Override
+    public void insertUpdate(DocumentEvent e) {
+        validate();
+    }
+
+    @Override
+    public void removeUpdate(DocumentEvent e) {
         validate();
     }
