Index: trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 15009)
+++ trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 15010)
@@ -17,4 +17,5 @@
 import java.lang.Character.UnicodeBlock;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -26,4 +27,5 @@
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import javax.swing.AbstractAction;
@@ -490,17 +492,65 @@
         }
 
+        /**
+         * Displays a warning message indicating that the upload comment is rejected.
+         * @param details details explaining why
+         * @return {@code true}
+         */
+        protected boolean warnRejectedUploadComment(String details) {
+            return warnRejectedUploadTag(
+                    tr("Please revise upload comment"),
+                    tr("Your upload comment is <i>rejected</i>.") + "<br />" + details
+            );
+        }
+
+        /**
+         * Displays a warning message indicating that the changeset source is rejected.
+         * @param details details explaining why
+         * @return {@code true}
+         */
+        protected boolean warnRejectedUploadSource(String details) {
+            return warnRejectedUploadTag(
+                    tr("Please revise changeset source"),
+                    tr("Your changeset source is <i>rejected</i>.") + "<br />" + details
+            );
+        }
+
+        /**
+         * Warn about an upload tag with the possibility of resuming the upload.
+         * @param title dialog title
+         * @param message dialog message
+         * @param togglePref preference entry to offer the user a "Do not show again" checkbox for the dialog
+         * @return {@code true} if the user wants to revise the upload tag
+         */
         protected boolean warnUploadTag(final String title, final String message, final String togglePref) {
-            String[] buttonTexts = new String[] {tr("Revise"), tr("Cancel"), tr("Continue as is")};
-            Icon[] buttonIcons = new Icon[] {
+            return warnUploadTag(title, message, togglePref, true);
+        }
+
+        /**
+         * Warn about an upload tag without the possibility of resuming the upload.
+         * @param title dialog title
+         * @param message dialog message
+         * @return {@code true}
+         */
+        protected boolean warnRejectedUploadTag(final String title, final String message) {
+            return warnUploadTag(title, message, null, false);
+        }
+
+        private boolean warnUploadTag(final String title, final String message, final String togglePref, boolean allowContinue) {
+            List<String> buttonTexts = new ArrayList<>(Arrays.asList(tr("Revise"), tr("Cancel")));
+            List<Icon> buttonIcons = new ArrayList<>(Arrays.asList(
                     new ImageProvider("ok").setMaxSize(ImageSizes.LARGEICON).get(),
-                    new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
-                    new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
-                            new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()};
-            String[] tooltips = new String[] {
+                    new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get()));
+            List<String> tooltips = new ArrayList<>(Arrays.asList(
                     tr("Return to the previous dialog to enter a more descriptive comment"),
-                    tr("Cancel and return to the previous dialog"),
-                    tr("Ignore this hint and upload anyway")};
-
-            ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts) {
+                    tr("Cancel and return to the previous dialog")));
+            if (allowContinue) {
+                buttonTexts.add(tr("Continue as is"));
+                buttonIcons.add(new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
+                        new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get());
+                tooltips.add(tr("Ignore this hint and upload anyway"));
+            }
+
+            ExtendedDialog dlg = new ExtendedDialog((Component) dialog, title, buttonTexts.toArray(new String[] {})) {
                 @Override
                 public void setupDialog() {
@@ -510,8 +560,10 @@
             };
             dlg.setContent("<html>" + message + "</html>");
-            dlg.setButtonIcons(buttonIcons);
-            dlg.setToolTipTexts(tooltips);
+            dlg.setButtonIcons(buttonIcons.toArray(new Icon[] {}));
+            dlg.setToolTipTexts(tooltips.toArray(new String[] {}));
             dlg.setIcon(JOptionPane.WARNING_MESSAGE);
-            dlg.toggleEnable(togglePref);
+            if (allowContinue) {
+                dlg.toggleEnable(togglePref);
+            }
             dlg.setCancelButton(1, 2);
             return dlg.showDialog().getValue() != 3;
@@ -542,4 +594,20 @@
         }
 
+        static String validateUploadTag(String uploadValue, String preferencePrefix) {
+            // Check mandatory terms
+            List<String> missingTerms = Config.getPref().getList(preferencePrefix+".mandatory-terms")
+                .stream().filter(x -> !uploadValue.contains(x)).collect(Collectors.toList());
+            if (!missingTerms.isEmpty()) {
+                return tr("The following required terms are missing: {0}", missingTerms);
+            }
+            // Check forbidden terms
+            List<String> forbiddenTerms = Config.getPref().getList(preferencePrefix+".forbidden-terms")
+                    .stream().filter(uploadValue::contains).collect(Collectors.toList());
+            if (!forbiddenTerms.isEmpty()) {
+                return tr("The following forbidden terms have been found: {0}", forbiddenTerms);
+            }
+            return null;
+        }
+
         @Override
         public void actionPerformed(ActionEvent e) {
@@ -547,11 +615,17 @@
             dialog.forceUpdateActiveField();
 
-            if (isUploadCommentTooShort(dialog.getUploadComment()) && warnUploadComment()) {
-                // abort for missing comment
+            final String uploadComment = dialog.getUploadComment();
+            final String uploadCommentRejection = validateUploadTag(uploadComment, "upload.comment");
+            if ((isUploadCommentTooShort(uploadComment) && warnUploadComment()) ||
+                (uploadCommentRejection != null && warnRejectedUploadComment(uploadCommentRejection))) {
+                // abort for missing or rejected comment
                 dialog.handleMissingComment();
                 return;
             }
-            if (dialog.getUploadSource().trim().isEmpty() && warnUploadSource()) {
-                // abort for missing changeset source
+            final String uploadSource = dialog.getUploadSource();
+            final String uploadSourceRejection = validateUploadTag(uploadSource, "upload.source");
+            if ((Utils.isStripEmpty(uploadSource) && warnUploadSource()) ||
+                    (uploadSourceRejection != null && warnRejectedUploadSource(uploadSourceRejection))) {
+                // abort for missing or rejected changeset source
                 dialog.handleMissingSource();
                 return;
@@ -562,6 +636,6 @@
             List<String> emptyChangesetTags = new ArrayList<>();
             for (final Entry<String, String> i : dialog.getTags(true).entrySet()) {
-                final boolean isKeyEmpty = i.getKey() == null || i.getKey().trim().isEmpty();
-                final boolean isValueEmpty = i.getValue() == null || i.getValue().trim().isEmpty();
+                final boolean isKeyEmpty = Utils.isStripEmpty(i.getKey());
+                final boolean isValueEmpty = Utils.isStripEmpty(i.getValue());
                 final boolean ignoreKey = "comment".equals(i.getKey()) || "source".equals(i.getKey());
                 if ((isKeyEmpty ^ isValueEmpty) && !ignoreKey) {
@@ -648,5 +722,5 @@
         if (evt.getPropertyName().equals(ChangesetManagementPanel.SELECTED_CHANGESET_PROP)) {
             Changeset cs = (Changeset) evt.getNewValue();
-            setChangesetTags(dataSet, cs == null); // keep comment/source of first tab for new changesets 
+            setChangesetTags(dataSet, cs == null); // keep comment/source of first tab for new changesets
             if (cs == null) {
                 tpConfigPanels.setTitleAt(1, tr("Tags of new changeset"));
Index: trunk/test/unit/org/openstreetmap/josm/gui/io/UploadDialogTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/gui/io/UploadDialogTest.java	(revision 15009)
+++ trunk/test/unit/org/openstreetmap/josm/gui/io/UploadDialogTest.java	(revision 15010)
@@ -21,4 +21,5 @@
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.io.UploadDialog.UploadAction;
 import org.openstreetmap.josm.io.UploadStrategySpecification;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -258,3 +259,28 @@
                 BasicUploadSettingsPanel.getDefaultSources().get(0));
     }
+
+    private static void doTestValidateUploadTag(String prefix) {
+        Config.getPref().putList(prefix + ".mandatory-terms", null);
+        Config.getPref().putList(prefix + ".forbidden-terms", null);
+        assertNull(UploadAction.validateUploadTag("foo", prefix));
+
+        Config.getPref().putList(prefix + ".mandatory-terms", Arrays.asList("foo"));
+        assertNull(UploadAction.validateUploadTag("foo", prefix));
+        assertEquals("The following required terms are missing: [foo]",
+                UploadAction.validateUploadTag("bar", prefix));
+
+        Config.getPref().putList(prefix + ".forbidden-terms", Arrays.asList("bar"));
+        assertNull(UploadAction.validateUploadTag("foo", prefix));
+        assertEquals("The following forbidden terms have been found: [bar]",
+                UploadAction.validateUploadTag("foobar", prefix));
+    }
+
+    /**
+     * Test of {@link UploadDialog.UploadAction#validateUploadTag} method.
+     */
+    @Test
+    public void testvalidateUploadTag() {
+        doTestValidateUploadTag("upload.comment");
+        doTestValidateUploadTag("upload.source");
+    }
 }
