Index: src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java	(revision 17296)
+++ src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java	(working copy)
@@ -90,7 +90,7 @@
      */
     private Optional<String> tryParseSearchTerm(String searchTerm) {
         try {
-            return Optional.of(SearchCompilerQueryWizard.getInstance().constructQuery(searchTerm));
+            return Optional.of(SearchCompilerQueryWizard.constructQuery(searchTerm));
         } catch (UncheckedParseException | IllegalStateException ex) {
             Logging.error(ex);
             JOptionPane.showMessageDialog(
Index: src/org/openstreetmap/josm/tools/SearchCompilerQueryWizard.java
===================================================================
--- src/org/openstreetmap/josm/tools/SearchCompilerQueryWizard.java	(revision 17296)
+++ src/org/openstreetmap/josm/tools/SearchCompilerQueryWizard.java	(working copy)
@@ -25,17 +25,6 @@
  */
 public final class SearchCompilerQueryWizard {
 
-    private static final SearchCompilerQueryWizard instance = new SearchCompilerQueryWizard();
-
-    /**
-     * Replies the unique instance of this class.
-     *
-     * @return the unique instance of this class
-     */
-    public static SearchCompilerQueryWizard getInstance() {
-        return instance;
-    }
-
     private SearchCompilerQueryWizard() {
         // private constructor for utility class
     }
@@ -46,7 +35,7 @@
      * @return an Overpass QL query
      * @throws UncheckedParseException when the parsing fails
      */
-    public String constructQuery(final String search) {
+    public static String constructQuery(final String search) {
         try {
             Matcher matcher = Pattern.compile("\\s+GLOBAL\\s*$", Pattern.CASE_INSENSITIVE).matcher(search);
             if (matcher.find()) {
@@ -73,7 +62,7 @@
                     throw new IllegalStateException(mode);
                 }
             }
-            
+
             final Match match = SearchCompiler.compile(search);
             return constructQuery(match, "[bbox:{{bbox}}];", "");
         } catch (SearchParseError | UnsupportedOperationException e) {
@@ -81,7 +70,7 @@
         }
     }
 
-    private String constructQuery(final Match match, final String bounds, final String queryLineSuffix) {
+    private static String constructQuery(final Match match, final String bounds, final String queryLineSuffix) {
         final List<Match> normalized = normalizeToDNF(match);
         final List<String> queryLines = new ArrayList<>();
         queryLines.add("[out:xml][timeout:90]" + bounds);
@@ -135,11 +124,15 @@
                     return "[" + (negated ? "!" : "") + quote(key) + "]";
                 case EXACT:
                     return "[" + quote(key) + (negated ? "!=" : "=") + quote(value) + "]";
+                case ANY_KEY: // *=value
+                    // fall through
                 case EXACT_REGEXP:
                     final Matcher matcher = Pattern.compile("/(?<regex>.*)/(?<flags>i)?").matcher(value);
                     final String valueQuery = matcher.matches()
                             ? quote(matcher.group("regex")) + Optional.ofNullable(matcher.group("flags")).map(f -> "," + f).orElse("")
                             : quote(value);
+                    if (mode == SearchCompiler.ExactKeyValue.Mode.ANY_KEY)
+                        return "[~\"^.*$\"" + (negated ? "!~" : "~") + valueQuery + "]";
                     return "[" + quote(key) + (negated ? "!~" : "~") + valueQuery + "]";
                 case MISSING_KEY:
                     // special case for empty values, see https://github.com/drolbr/Overpass-API/issues/53
Index: test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java	(revision 17296)
+++ test/unit/org/openstreetmap/josm/io/OverpassDownloadReaderTest.java	(working copy)
@@ -58,7 +58,7 @@
     }
 
     private String getExpandedQuery(String search) {
-        final String query = SearchCompilerQueryWizard.getInstance().constructQuery(search);
+        final String query = SearchCompilerQueryWizard.constructQuery(search);
         final String request = new OverpassDownloadReader(new Bounds(1, 2, 3, 4), null, query)
                 .getRequestForBbox(1, 2, 3, 4)
                 .substring("interpreter?data=".length());
Index: test/unit/org/openstreetmap/josm/tools/SearchCompilerQueryWizardTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/tools/SearchCompilerQueryWizardTest.java	(revision 17296)
+++ test/unit/org/openstreetmap/josm/tools/SearchCompilerQueryWizardTest.java	(working copy)
@@ -23,7 +23,7 @@
     public JOSMTestRules test = new JOSMTestRules().i18n("de");
 
     private static String constructQuery(String s) {
-        return SearchCompilerQueryWizard.getInstance().constructQuery(s);
+        return SearchCompilerQueryWizard.constructQuery(s);
     }
 
     private void assertQueryEquals(String expectedQueryPart, String input) {
@@ -240,4 +240,15 @@
         assertQueryEquals("  relation[\"type\"=\"multipolygon\"][!\"landuse\"][!\"area:highway\"];\n",
                 "type:relation and type=multipolygon and -landuse=* and -\"area:highway\"=*");
     }
+
+    /**
+     * Test for ticket <a href="https://josm.openstreetmap.de/ticket/20037>#20037</a>
+     */
+    @Test
+    void testTicket20037() {
+        assertQueryEquals(
+                "  node[~\"^.*$\"~\"forward\"];\n" +
+                "  node[~\"^.*$\"~\"backward\"];\n",
+                "type:node AND (*=forward OR *=backward)");
+    }
 }
