diff --git a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
index c5b17638f1..ca5773ef0b 100644
--- a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
+++ b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
@@ -290,6 +290,16 @@ public class SearchCompiler {
         public final boolean test(OsmPrimitive object) {
             return match(object);
         }
+
+        /**
+         * Check if this is a valid match object
+         * @return {@code this}, for easy chaining
+         * @throws SearchParseError If the match is not valid
+         */
+        public Match validate() throws SearchParseError {
+            // Default to no-op
+            return this;
+        }
     }
 
     public abstract static class TaggedMatch extends Match {
@@ -861,6 +871,14 @@ public class SearchCompiler {
                 return false;
             return true;
         }
+
+        @Override
+        public Match validate() throws SearchParseError {
+            if (this.referenceValue == null) {
+                throw new SearchParseError(tr("Reference value must not be null"));
+            }
+            return this;
+        }
     }
 
     /**
@@ -2108,13 +2126,13 @@ public class SearchCompiler {
             // factor consists of key:value or key=value
             String key = tokenizer.getText();
             if (tokenizer.readIfEqual(Token.EQUALS)) {
-                return new ExactKeyValue(regexSearch, caseSensitive, key, tokenizer.readTextOrNumber());
+                return new ExactKeyValue(regexSearch, caseSensitive, key, tokenizer.readTextOrNumber()).validate();
             } else if (tokenizer.readIfEqual(Token.TILDE)) {
-                return new ExactKeyValue(true, caseSensitive, key, tokenizer.readTextOrNumber());
+                return new ExactKeyValue(true, caseSensitive, key, tokenizer.readTextOrNumber()).validate();
             } else if (tokenizer.readIfEqual(Token.LESS_THAN)) {
-                return new ValueComparison(key, tokenizer.readTextOrNumber(), -1);
+                return new ValueComparison(key, tokenizer.readTextOrNumber(), -1).validate();
             } else if (tokenizer.readIfEqual(Token.GREATER_THAN)) {
-                return new ValueComparison(key, tokenizer.readTextOrNumber(), +1);
+                return new ValueComparison(key, tokenizer.readTextOrNumber(), +1).validate();
             } else if (tokenizer.readIfEqual(Token.COLON)) {
                 // see if we have a Match that takes a data parameter
                 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
@@ -2123,24 +2141,24 @@ public class SearchCompiler {
 
                 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
                 if (unaryFactory != null)
-                    return unaryFactory.get(key, parseFactor(), tokenizer);
+                    return unaryFactory.get(key, parseFactor(), tokenizer).validate();
 
                 // key:value form where value is a string (may be OSM key search)
                 final String value = tokenizer.readTextOrNumber();
-                return new KeyValue(key, value != null ? value : "", regexSearch, caseSensitive);
+                return new KeyValue(key, value != null ? value : "", regexSearch, caseSensitive).validate();
             } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
                 return new BooleanMatch(key, false);
             else {
                 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
                 if (factory != null)
-                    return factory.get(key, caseSensitive, regexSearch, null);
+                    return factory.get(key, caseSensitive, regexSearch, null).validate();
 
                 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
                 if (unaryFactory != null)
-                    return unaryFactory.get(key, parseFactor(), null);
+                    return unaryFactory.get(key, parseFactor(), null).validate();
 
                 // match string in any key or value
-                return new Any(key, regexSearch, caseSensitive);
+                return new Any(key, regexSearch, caseSensitive).validate();
             }
         } else
             return null;
diff --git a/test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java b/test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java
index 9545c88cd7..1e34009fb6 100644
--- a/test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java
+++ b/test/unit/org/openstreetmap/josm/data/osm/search/SearchCompilerTest.java
@@ -18,6 +18,13 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
 
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -42,12 +49,6 @@ import org.openstreetmap.josm.gui.tagging.presets.items.Key;
 import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
 import org.openstreetmap.josm.tools.Logging;
 
-import nl.jqno.equalsverifier.EqualsVerifier;
-import nl.jqno.equalsverifier.Warning;
-import org.junit.Assert;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-
 /**
  * Unit tests for class {@link SearchCompiler}.
  */
@@ -828,4 +829,13 @@ class SearchCompilerTest {
         sc.match(sc.n1, true);
         sc.match(sc.n2, false);
     }
+
+    /**
+     * Non-regression test for JOSM #21300
+     */
+    @ParameterizedTest
+    @ValueSource(strings = {"maxweight<" /* #21300 */, "maxweight>"})
+    void testNonRegression21300(final String searchString) {
+        assertThrows(SearchParseError.class, () -> SearchCompiler.compile(searchString));
+    }
 }
