Index: /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 17348)
+++ /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 17349)
@@ -45,4 +45,5 @@
 import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations;
 import org.openstreetmap.josm.data.validation.tests.CrossingWays;
+import org.openstreetmap.josm.data.validation.tests.DirectionNodes;
 import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
 import org.openstreetmap.josm.data.validation.tests.DuplicateRelation;
@@ -152,4 +153,5 @@
         SharpAngles.class, // 3800 .. 3899
         ConnectivityRelations.class, // 3900 .. 3999
+        DirectionNodes.class, // 4000-4099
     };
 
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java	(revision 17349)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java	(revision 17349)
@@ -0,0 +1,87 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+
+/**
+ * Find nodes with direction tag and invalid number of parent ways or position in way. See #20019.
+ * @author Gerd Petermann
+ * @since 17349
+ */
+public class DirectionNodes extends Test {
+    private static final int MULTIPLE_WAYS_CODE = 4000;
+    private static final int END_NODE_CODE = 4001;
+    private static final int NO_WAY_CODE = 4002;
+
+    private static final String DIR_VERIF_PROBLEM_MSG = tr("Invalid usage of direction on node");
+
+    /**
+     * Construct a new {@code DirectionNodes} object
+     */
+    public DirectionNodes() {
+        super(tr("Direction nodes"), tr("Check for nodes which have a 'forward' or 'backward' direction"));
+    }
+
+    @Override
+    public void visit(Node n) {
+        if (!n.isUsable() || !n.isTagged())
+            return;
+        for (Entry<String, String> tag : n.getKeys().entrySet()) {
+            if (("forward".equals(tag.getValue()) || "backward".equals(tag.getValue())
+                    && ("direction".equals(tag.getKey()) || tag.getKey().endsWith(":direction")))) {
+                checkParents(n, tag.toString());
+            }
+        }
+    }
+
+    private static boolean isSuitableParentWay(Way w) {
+        return w.hasKey("highway", "railway", "waterway") || w.hasTag("man_made", "pipeline");
+    }
+
+    private void checkParents(Node n, String tag) {
+        final List<Way> ways = new ArrayList<>();
+        int count = 0;
+        for (Way w : n.getParentWays()) {
+            if (isSuitableParentWay(w)) {
+                ways.add(w);
+            }
+            count++;
+        }
+        boolean needsParentWays = n.isNew() || (!n.isOutsideDownloadArea() && n.getDataSet().getDataSourceArea() != null);
+        TestError.Builder builder = null;
+        if (ways.isEmpty() && needsParentWays) {
+            if (count == 0) {
+                builder = TestError.builder(this, Severity.WARNING, NO_WAY_CODE).message(DIR_VERIF_PROBLEM_MSG,
+                        marktr("Unconnected node with {0}"), tag);
+            }
+
+        } else if (ways.size() == 1) {
+            Way w = ways.get(0);
+            if (w.firstNode() == n || w.lastNode() == n) {
+                builder = TestError.builder(this, Severity.WARNING, END_NODE_CODE).message(DIR_VERIF_PROBLEM_MSG,
+                        marktr("Node with {0} on end of way"), tag);
+            }
+        } else if (ways.size() > 1) {
+            builder = TestError.builder(this, Severity.WARNING, MULTIPLE_WAYS_CODE).message(DIR_VERIF_PROBLEM_MSG,
+                    marktr("Node with {0} on a connection of multiple ways"), tag);
+        }
+        if (builder != null) {
+            List<OsmPrimitive> primitives = new ArrayList<>();
+            primitives.add(n);
+            primitives.addAll(ways);
+            errors.add(builder.primitives(primitives).highlight(n).build());
+        }
+    }
+}
