Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 18490)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 18491)
@@ -222,4 +222,8 @@
         );
 
+        // Set the initial state of the upload button
+        btnUpload.setEnabled(pnlBasicUploadSettings.getUploadTextValidators()
+                .stream().noneMatch(UploadTextComponentValidator::isUploadRejected));
+
         // Enable/disable the upload button if at least an upload validator rejects upload
         pnlBasicUploadSettings.getUploadTextValidators().forEach(v -> v.addChangeListener(e -> btnUpload.setEnabled(
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java	(revision 18490)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadTextComponentValidator.java	(revision 18491)
@@ -84,6 +84,15 @@
                 return;
             }
-            String uploadComment = getComponent().getText();
-            if (UploadDialog.UploadAction.isUploadCommentTooShort(uploadComment)) {
+            final String uploadComment = getComponent().getText();
+            final String rejection = UploadDialog.UploadAction.validateUploadTag(uploadComment, "upload.comment",
+                    Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+
+            // Reject the upload if tags are required and are not in the input. If the tags exist or are not
+            // required, then check the length of the input and warn if it's too short (a short msg is not a rejection)
+            uploadRejected = rejection != null;
+
+            if (uploadRejected) {
+                feedbackWarning(tr("Your upload comment is <i>rejected</i>.") + "<br />" + rejection);
+            } else if (UploadDialog.UploadAction.isUploadCommentTooShort(uploadComment)) {
                 feedbackWarning(tr("Your upload comment is <i>empty</i>, or <i>very short</i>.<br /><br />" +
                         "This is technically allowed, but please consider that many users who are<br />" +
@@ -93,13 +102,6 @@
                         "easier for many other mappers.").replace("<br />", " "));
             } else {
-                String rejection = UploadDialog.UploadAction.validateUploadTag(uploadComment, "upload.comment",
-                        Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
-                uploadRejected = rejection != null;
-                if (uploadRejected) {
-                    feedbackWarning(tr("Your upload comment is <i>rejected</i>.") + "<br />" + rejection);
-                } else {
-                    feedbackValid(tr("Thank you for providing a changeset comment! " +
-                            "This gives other mappers a better understanding of your intent."));
-                }
+                feedbackValid(tr("Thank you for providing a changeset comment! " +
+                        "This gives other mappers a better understanding of your intent."));
             }
         }
@@ -121,6 +123,15 @@
                 return;
             }
-            String uploadSource = getComponent().getText();
-            if (Utils.isStripEmpty(uploadSource)) {
+            final String uploadSource = getComponent().getText();
+            final String rejection = UploadDialog.UploadAction.validateUploadTag(
+                    uploadSource, "upload.source", Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+
+            // Reject the upload only if tags are required and are not in the input. If the tags exist or are not
+            // required, then check the length of the input and warn if it's too short (a short msg is not a rejection)
+            uploadRejected = rejection != null;
+
+            if (uploadRejected) {
+                feedbackWarning(tr("Your changeset source is <i>rejected</i>.") + "<br />" + rejection);
+            } else if (Utils.isStripEmpty(uploadSource)) {
                 feedbackWarning(tr("You did not specify a source for your changes.<br />" +
                         "It is technically allowed, but this information helps<br />" +
@@ -129,12 +140,5 @@
                         "easier for many other mappers.").replace("<br />", " "));
             } else {
-                final String rejection = UploadDialog.UploadAction.validateUploadTag(
-                        uploadSource, "upload.source", Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
-                uploadRejected = rejection != null;
-                if (uploadRejected) {
-                    feedbackWarning(tr("Your changeset source is <i>rejected</i>.") + "<br />" + rejection);
-                } else {
-                    feedbackValid(tr("Thank you for providing the data source!"));
-                }
+                feedbackValid(tr("Thank you for providing the data source!"));
             }
         }
Index: /trunk/test/unit/org/openstreetmap/josm/gui/io/UploadTextComponentValidatorTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/io/UploadTextComponentValidatorTest.java	(revision 18490)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/io/UploadTextComponentValidatorTest.java	(revision 18491)
@@ -5,8 +5,16 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import java.util.Arrays;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
 import javax.swing.JLabel;
 import javax.swing.JTextField;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
 
@@ -40,3 +48,41 @@
         assertThat(feedback.getText(), containsString("Thank you for providing the data source"));
     }
+
+    static Stream<Arguments> testUploadWithMandatoryTerm() {
+        return Stream.of(Arguments.of("upload.comment.mandatory-terms", "Thank you for providing a changeset comment",
+                    (BiFunction<JTextField, JLabel, ? extends UploadTextComponentValidator>)
+                            UploadTextComponentValidator.UploadCommentValidator::new),
+                Arguments.of("upload.source.mandatory-terms", "Thank you for providing the data source",
+                    (BiFunction<JTextField, JLabel, ? extends UploadTextComponentValidator>)
+                            UploadTextComponentValidator.UploadSourceValidator::new)
+        );
+    }
+
+    /**
+     * Unit test of {@link UploadTextComponentValidator.UploadCommentValidator} and
+     * {@link UploadTextComponentValidator.UploadSourceValidator} with mandatory terms
+     */
+    @BasicPreferences
+    @ParameterizedTest
+    @MethodSource
+    void testUploadWithMandatoryTerm(String confPref, String expectedText,
+            BiFunction<JTextField, JLabel, ? extends UploadTextComponentValidator> validatorSupplier) {
+        Config.getPref().putList(confPref, Arrays.asList("myrequired", "xyz"));
+        JTextField textField = new JTextField("");
+        JLabel feedback = new JLabel();
+
+        validatorSupplier.apply(textField, feedback);
+
+        // A too-short string should fail validation
+        textField.setText("");
+        assertThat(feedback.getText(), containsString("The following required terms are missing: [myrequired, xyz]"));
+
+        // A long enough string without the mandatory terms should claim that the required terms are missing
+        textField.setText("a string long enough but missing the mandatory term");
+        assertThat(feedback.getText(), containsString("The following required terms are missing: [myrequired, xyz]"));
+
+        // A valid string should pass
+        textField.setText("a string long enough with the mandatory term #myrequired #xyz");
+        assertThat(feedback.getText(), containsString(expectedText));
+    }
 }
Index: /trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java	(revision 18490)
+++ /trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java	(revision 18491)
@@ -57,4 +57,5 @@
             // Disable saving on put, just to avoid overwriting pref files
             pref.enableSaveOnPut(false);
+            pref.resetToDefault();
             Config.setPreferencesInstance(pref);
             Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance());
