Index: src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 16199)
+++ src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(working copy)
@@ -127,6 +127,12 @@
     protected static final short FLAG_PRESERVED = 1 << 13;
 
     /**
+     * Determines if the primitive refers to incomplete members
+     * Members can be nodes of a way or members of a relation.
+     */
+    protected static final short FLAG_INCOMPLETE_MEMBERS = 1 << 14;
+
+    /**
      * Put several boolean flags to one short int field to save memory.
      * Other bits of this field are used in subclasses.
      */
Index: src/org/openstreetmap/josm/data/osm/Node.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/Node.java	(revision 16199)
+++ src/org/openstreetmap/josm/data/osm/Node.java	(working copy)
@@ -434,4 +434,9 @@
     protected void updateDirectionFlags() {
         // Nodes do not need/have a direction, greatly improves performance, see #18886
     }
+
+    @Override
+    void childIncompleteStatusWillChange(OsmPrimitive osmPrimitive) {
+        // should not happen, nodes don't have children
+    }
 }
Index: src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 16199)
+++ src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(working copy)
@@ -485,11 +485,14 @@
         checkDatasetNotReadOnly();
         boolean locked = writeLock();
         try {
-            if (dataSet != null && incomplete != this.isIncomplete()) {
-                if (incomplete) {
-                    dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
-                } else {
-                    dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
+            if (incomplete != this.isIncomplete()) {
+                referrers(true, OsmPrimitive.class).forEach(ref -> ref.childIncompleteStatusWillChange(this));
+                if (dataSet != null) {
+                    if (incomplete) {
+                        dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
+                    } else {
+                        dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
+                    }
                 }
             }
             super.setIncomplete(incomplete);
@@ -498,6 +501,12 @@
         }
     }
 
+    /**
+     * Called when the incomplete status of the primitive was changed.
+     * @param osmPrimitive before the change
+     */
+    abstract void childIncompleteStatusWillChange(OsmPrimitive osmPrimitive);
+
     @Override
     public boolean isSelected() {
         return dataSet != null && dataSet.isSelected(this);
@@ -837,8 +846,8 @@
             OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
             for (OsmPrimitive ref: refs) {
                 if (ref.dataSet == dataSet) {
-                    visitor.accept(ref);
                 }
+                visitor.accept(ref);
             }
         }
     }
Index: src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/Relation.java	(revision 16199)
+++ src/org/openstreetmap/josm/data/osm/Relation.java	(working copy)
@@ -54,11 +54,13 @@
             } else {
                 this.members = new RelationMember[0];
             }
+            boolean hasIncompleteMember = false;
             for (RelationMember rm : this.members) {
                 rm.getMember().addReferrer(this);
                 rm.getMember().clearCachedStyle();
+                hasIncompleteMember |= (rm.getMember().isIncomplete());
             }
-
+            setIncompleteMembers(hasIncompleteMember);
             fireMembersChanged();
         } finally {
             writeUnlock(locked);
@@ -84,6 +86,8 @@
         boolean locked = writeLock();
         try {
             members = Utils.addInArrayCopy(members, member);
+            if (member.getMember().isIncomplete())
+                setIncompleteMembers(true);
             member.getMember().addReferrer(this);
             member.getMember().clearCachedStyle();
             fireMembersChanged();
@@ -106,6 +110,8 @@
             System.arraycopy(members, index, newMembers, index + 1, members.length - index);
             newMembers[index] = member;
             members = newMembers;
+            if (member.getMember().isIncomplete())
+                setIncompleteMembers(true);
             member.getMember().addReferrer(this);
             member.getMember().clearCachedStyle();
             fireMembersChanged();
@@ -129,6 +135,11 @@
             if (originalMember.getMember() != member.getMember()) {
                 member.getMember().addReferrer(this);
                 member.getMember().clearCachedStyle();
+                if (!originalMember.getMember().isIncomplete() && member.getMember().isIncomplete())
+                    setIncompleteMembers(true);
+                else if (originalMember.getMember().isIncomplete() && !member.getMember().isIncomplete()) {
+                    setIncompleteMembers(Stream.of(members).anyMatch(m -> m.getMember().isIncomplete()));
+                }
                 originalMember.getMember().removeReferrer(this);
                 originalMember.getMember().clearCachedStyle();
                 fireMembersChanged();
@@ -493,12 +504,18 @@
         }
     }
 
+    /**
+     * If set to true, this object refers to one or more incomplete members.
+     * Members can be nodes of a way or members of a relation.
+     * @param incomplete incomplete flag value
+     */
+    private void setIncompleteMembers(boolean incomplete) {
+        updateFlags(FLAG_INCOMPLETE_MEMBERS, incomplete);
+    }
+
     @Override
     public boolean hasIncompleteMembers() {
-        for (RelationMember rm: members) {
-            if (rm.getMember().isIncomplete()) return true;
-        }
-        return false;
+        return (flags & FLAG_INCOMPLETE_MEMBERS) != 0;
     }
 
     /**
@@ -508,14 +525,20 @@
      */
     @Override
     public Collection<OsmPrimitive> getIncompleteMembers() {
-        Set<OsmPrimitive> ret = new HashSet<>();
-        for (RelationMember rm: members) {
-            if (!rm.getMember().isIncomplete()) {
-                continue;
+        return Stream.of(members).map(RelationMember::getMember).filter(OsmPrimitive::isIncomplete)
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    void childIncompleteStatusWillChange(OsmPrimitive osmPrimitive) {
+        for (RelationMember originalMember : getMembersFor(Collections.singleton(osmPrimitive))) {
+            if (!originalMember.getMember().isIncomplete())
+                setIncompleteMembers(true);
+            else if (originalMember.getMember().isIncomplete()) {
+                setIncompleteMembers(Stream.of(members).filter(m -> m != originalMember)
+                        .anyMatch(m -> m.getMember().isIncomplete()));
             }
-            ret.add(rm.getMember());
         }
-        return ret;
     }
 
     @Override
Index: src/org/openstreetmap/josm/data/osm/Way.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/Way.java	(revision 16199)
+++ src/org/openstreetmap/josm/data/osm/Way.java	(working copy)
@@ -56,10 +56,13 @@
             } else {
                 this.nodes = nodes.toArray(new Node[0]);
             }
+            boolean hasIncompleteNodes = false;
             for (Node node: this.nodes) {
                 node.addReferrer(this);
                 node.clearCachedStyle();
+                hasIncompleteNodes |= node.isIncomplete();
             }
+            setIncompleteNodes(hasIncompleteNodes);
 
             clearCachedStyle();
             fireNodesChanged();
@@ -69,6 +72,15 @@
     }
 
     /**
+     * If set to true, this object refers to one or more incomplete members.
+     * Members can be nodes of a way or members of a relation.
+     * @param incomplete incomplete flag value
+     */
+    private void setIncompleteNodes(boolean incomplete) {
+        updateFlags(FLAG_INCOMPLETE_MEMBERS, incomplete);
+    }
+
+    /**
      * Prevent directly following identical nodes in ways.
      * @param nodes list of nodes
      * @return {@code nodes} with consecutive identical nodes removed
@@ -401,6 +413,8 @@
             clearCachedStyle();
             n.addReferrer(this);
             nodes = Utils.addInArrayCopy(nodes, n);
+            if (n.isIncomplete())
+                setIncompleteNodes(true);
             n.clearCachedStyle();
             fireNodesChanged();
         } finally {
@@ -434,6 +448,8 @@
             System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
             newNodes[offs] = n;
             nodes = newNodes;
+            if (n.isIncomplete())
+                setIncompleteNodes(true);
             n.clearCachedStyle();
             fireNodesChanged();
         } finally {
@@ -590,7 +606,7 @@
      * @since 2587
      */
     public boolean hasIncompleteNodes() {
-        return Arrays.stream(nodes).anyMatch(Node::isIncomplete);
+        return (flags & FLAG_INCOMPLETE_MEMBERS) != 0;
     }
 
     /**
@@ -755,4 +771,16 @@
     public UniqueIdGenerator getIdGenerator() {
         return idGenerator;
     }
+
+    @Override
+    void childIncompleteStatusWillChange(OsmPrimitive osmPrimitive) {
+        if (hasIncompleteNodes() && osmPrimitive.isIncomplete()) {
+            for (Node n : nodes) {
+                if (n != osmPrimitive && n.isIncomplete()) {
+                    setIncomplete(true);
+                    break;
+                }
+            }
+        }
+    }
 }
Index: test/unit/org/openstreetmap/josm/data/osm/RelationTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/osm/RelationTest.java	(revision 16199)
+++ test/unit/org/openstreetmap/josm/data/osm/RelationTest.java	(working copy)
@@ -154,4 +154,47 @@
     public void testLoadIAE() {
         new Relation().load(new NodeData());
     }
+
+    /**
+     * Test {@link Relation#hasIncompleteNodes()}
+     */
+    @Test
+    public void testIncompleteMembers() {
+        DataSet ds = new DataSet();
+
+        Node n1 = new Node(new LatLon(10, 10));
+        Node n2 = new Node(new LatLon(20, 20));
+        Way w1 = new Way();
+        w1.addNode(n1);
+        w1.addNode(n2);
+        Relation r1 = new Relation();
+        ds.addPrimitive(r1);
+        ds.addPrimitive(n1);
+        ds.addPrimitive(n2);
+        ds.addPrimitive(w1);
+        r1.addMember(new RelationMember("", n1));
+        r1.addMember(new RelationMember("", w1));
+        r1.addMember(new RelationMember("", r1));
+        assertFalse(r1.hasIncompleteMembers());
+        Node n3 = new Node(1); // incomplete node
+        ds.addPrimitive(n3);
+        r1.addMember(new RelationMember("", n3));
+        assertTrue(r1.hasIncompleteMembers());
+        r1.removeMembersFor(n3);
+        assertFalse(r1.hasIncompleteMembers());
+        r1.addMember(1, new RelationMember("", n3));
+        assertTrue(r1.hasIncompleteMembers());
+        r1.removeMember(1);
+        assertFalse(r1.hasIncompleteMembers());
+        r1.addMember(1, new RelationMember("", n3));
+        assertTrue(r1.hasIncompleteMembers());
+        r1.setMember(1, new RelationMember("", n2));
+        assertFalse(r1.hasIncompleteMembers());
+        r1.setMember(1, new RelationMember("", n3));
+        assertTrue(r1.hasIncompleteMembers());
+        n3.setCoor(new LatLon(30, 30));
+        n3.setIncomplete(false);
+        assertFalse(r1.hasIncompleteMembers());
+    }
+
 }
Index: test/unit/org/openstreetmap/josm/data/osm/WayTest.java
===================================================================
--- test/unit/org/openstreetmap/josm/data/osm/WayTest.java	(revision 16199)
+++ test/unit/org/openstreetmap/josm/data/osm/WayTest.java	(working copy)
@@ -64,10 +64,34 @@
     }
 
     /**
-     * Test that {@link Way#load} throws IAE for invalid arguments
+     * Test {@link Way#hasIncompleteNodes()}
      */
-    @Test(expected = IllegalArgumentException.class)
-    public void testLoadIAE() {
-        new Way().load(new NodeData());
+    @Test
+    public void testIncompleteNodes() {
+        DataSet ds = new DataSet();
+        Node n1 = new Node(1);
+        Node n2 = new Node(2);
+        n1.setIncomplete(true);
+        n2.setCoor(new LatLon(20, 20));
+        n2.setIncomplete(false);
+        Way way = new Way(1);
+        way.setIncomplete(false);
+        ds.addPrimitive(n1);
+        ds.addPrimitive(n2);
+        ds.addPrimitive(way);
+        assertFalse(way.hasIncompleteNodes());
+        way.setNodes(Arrays.asList(n1));
+        assertTrue(way.hasIncompleteNodes());
+        way.setNodes(Arrays.asList(n2));
+        assertFalse(way.hasIncompleteNodes());
+        way.setNodes(Arrays.asList(n1, n2));
+        assertTrue(way.hasIncompleteNodes());
+        way.removeNode(n1);
+        assertFalse(way.hasIncompleteNodes());
+        way.addNode(n1);
+        assertTrue(way.hasIncompleteNodes());
+        n1.setCoor(new LatLon(10, 10));
+        n1.setIncomplete(false);
+        assertFalse(way.hasIncompleteNodes());
     }
 }
