Index: lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision 1694685)
+++ lucene/join/src/java/org/apache/lucene/search/join/ToParentBlockJoinQuery.java	(revision )
@@ -270,12 +270,10 @@
 
         // Parent & child docs are supposed to be
         // orthogonal:
-        if (nextChildDoc == parentDoc) {
-          throw new IllegalStateException("child query must only match non-parent docs, but parent docID=" + nextChildDoc + " matched childScorer=" + childScorer.getClass());
-        }
+        checkParentChildOrthogonality(nextChildDoc, parentDoc);
 
         //System.out.println("  parentDoc=" + parentDoc);
-        assert parentDoc != DocIdSetIterator.NO_MORE_DOCS;
+        //assert parentDoc != DocIdSetIterator.NO_MORE_DOCS;
 
         //System.out.println("  nextChildDoc=" + nextChildDoc);
         if (acceptDocs != null && !acceptDocs.get(parentDoc)) {
@@ -287,9 +285,7 @@
 
           // Parent & child docs are supposed to be
           // orthogonal:
-          if (nextChildDoc == parentDoc) {
-            throw new IllegalStateException("child query must only match non-parent docs, but parent docID=" + nextChildDoc + " matched childScorer=" + childScorer.getClass());
-          }
+          checkParentChildOrthogonality(nextChildDoc, parentDoc);
 
           continue;
         }
@@ -330,9 +326,7 @@
 
         // Parent & child docs are supposed to be
         // orthogonal:
-        if (nextChildDoc == parentDoc) {
-          throw new IllegalStateException("child query must only match non-parent docs, but parent docID=" + nextChildDoc + " matched childScorer=" + childScorer.getClass());
-        }
+        checkParentChildOrthogonality(nextChildDoc, parentDoc);
 
         switch(scoreMode) {
         case Avg:
@@ -401,13 +395,26 @@
       }
 
       // Parent & child docs are supposed to be orthogonal:
-      if (nextChildDoc == prevParentDoc) {
-        throw new IllegalStateException("child query must only match non-parent docs, but parent docID=" + nextChildDoc + " matched childScorer=" + childScorer.getClass());
-      }
+      checkParentChildOrthogonality(nextChildDoc, prevParentDoc);
 
       final int nd = nextDoc();
       //System.out.println("  return nextParentDoc=" + nd);
       return nd;
+    }
+
+    private void checkParentChildOrthogonality(int childDoc, int parentDoc) {
+      if (childDoc == parentDoc) {
+        if (childDoc == NO_MORE_DOCS) {
+          throw new IllegalStateException("Next child document is not found in current segment." +
+                  " This happens when child documents of the same parent are split into different segments." +
+                  " Please ensure that parent documents are indexed together with their children in a single block.");
+        } else {
+          ToParentBlockJoinQuery query = (ToParentBlockJoinQuery) this.weight.getQuery();
+          throw new IllegalStateException("Child query must only match non-parent docs, but parent docID=" + childDoc +
+                  ", produced by filter=\"" + query.parentsFilter + "\"," +
+                  " is matched by child query=\"" + query.origChildQuery + "\"");
+        }
+      }
     }
 
     public Explanation explain(int docBase) throws IOException {
Index: lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java	(revision 1694685)
+++ lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java	(revision )
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -53,20 +54,48 @@
   @Override
   public void setUp() throws Exception {
     super.setUp();
+    init(true);
+  }
+
+  private void init(boolean indexDocsCorrectly) throws IOException {
     directory = newDirectory();
     final IndexWriterConfig config = new IndexWriterConfig(new MockAnalyzer(random()));
-    final IndexWriter indexWriter = new IndexWriter(directory, config);
-    for (int i = 0; i < AMOUNT_OF_SEGMENTS; i++) {
-      List<Document> segmentDocs = createDocsForSegment(i);
-      indexWriter.addDocuments(segmentDocs);
-      indexWriter.commit();
-    }
+    IndexWriter indexWriter = new IndexWriter(directory, config);
+    indexDocs(indexWriter, indexDocsCorrectly);
     indexReader = DirectoryReader.open(indexWriter, random().nextBoolean());
     indexWriter.close();
     indexSearcher = new IndexSearcher(indexReader);
     parentsFilter = new BitDocIdSetCachingWrapperFilter(new QueryWrapperFilter(new WildcardQuery(new Term("parent", "*"))));
   }
 
+  private void indexDocs(IndexWriter indexWriter, boolean indexDocsCorrectly) throws IOException {
+    if (indexDocsCorrectly) {
+      indexDocsForSegments(indexWriter, 0, AMOUNT_OF_SEGMENTS);
+    } else {
+        int poisonedSegmentNumber = random().nextInt(AMOUNT_OF_SEGMENTS-2);
+        indexDocsForSegments(indexWriter, 0, poisonedSegmentNumber);
+        int poisonedParent = random().nextInt(AMOUNT_OF_PARENT_DOCS);
+        // split next block onto 2 segments
+        List<Document> segmentDocs = createDocsForSegment(poisonedSegmentNumber);
+        int numberOfSplitChildDocs = random().nextInt(AMOUNT_OF_CHILD_DOCS) + 1;
+        int numberOfDocsInFirstHalf = poisonedParent * (AMOUNT_OF_CHILD_DOCS + 1) + numberOfSplitChildDocs;
+        indexWriter.addDocuments(segmentDocs.subList(0, numberOfDocsInFirstHalf));
+        indexWriter.commit();
+        indexWriter.addDocuments(segmentDocs.subList(numberOfDocsInFirstHalf, segmentDocs.size()));
+        indexWriter.commit();
+        // index rest of segments
+        indexDocsForSegments(indexWriter, poisonedSegmentNumber + 1, AMOUNT_OF_SEGMENTS);
+      }
+  }
+
+  void indexDocsForSegments(IndexWriter indexWriter, int fromSegment, int toSegment) throws IOException {
+    for (int i = fromSegment; i < toSegment; i++) {
+      List<Document> segmentDocs = createDocsForSegment(i);
+      indexWriter.addDocuments(segmentDocs);
+      indexWriter.commit();
+    }
+  }
+
   @Override
   public void tearDown() throws Exception {
     indexReader.close();
@@ -81,7 +110,7 @@
       indexSearcher.search(blockJoinQuery, 1);
       fail("didn't get expected exception");
     } catch (IllegalStateException expected) {
-      assertTrue(expected.getMessage() != null && expected.getMessage().contains("child query must only match non-parent docs"));
+      assertTrue(expected.getMessage() != null && expected.getMessage().contains("Child query must only match non-parent docs"));
     }
   }
 
@@ -102,7 +131,7 @@
       indexSearcher.search(conjunctionQuery, 1);
       fail("didn't get expected exception");
     } catch (IllegalStateException expected) {
-      assertTrue(expected.getMessage() != null && expected.getMessage().contains("child query must only match non-parent docs"));
+      assertTrue(expected.getMessage() != null && expected.getMessage().contains("Child query must only match non-parent docs"));
     }
   }
 
@@ -140,7 +169,20 @@
     }
   }
 
-  private static List<Document> createDocsForSegment(int segmentNumber) {
+  public void testWrongIndexingValidation() throws Exception {
+    tearDown();
+    init(false);
+    Query childQuery = new WildcardQuery(new Term("child", "*"));
+    ToParentBlockJoinQuery blockJoinQuery = new ToParentBlockJoinQuery(childQuery, parentsFilter, ScoreMode.None);
+    try {
+      indexSearcher.search(blockJoinQuery, 1);
+      fail("didn't get expected exception");
+    } catch (IllegalStateException expected) {
+      assertTrue(expected.getMessage() != null && expected.getMessage().contains("Next child document is not found in current segment." ));
+    }
+  }
+
+  static List<Document> createDocsForSegment(int segmentNumber) {
     List<List<Document>> blocks = new ArrayList<>(AMOUNT_OF_PARENT_DOCS);
     for (int i = 0; i < AMOUNT_OF_PARENT_DOCS; i++) {
       blocks.add(createParentDocWithChildren(segmentNumber, i));
@@ -203,10 +245,6 @@
     childQueryWithRandomParent.add(new BooleanClause(parentsQuery, BooleanClause.Occur.SHOULD));
     childQueryWithRandomParent.add(new BooleanClause(randomChildQuery(randomChildNumber), BooleanClause.Occur.SHOULD));
     return childQueryWithRandomParent;
-  }
-
-  private static Query createParentQuery() {
-    return new TermQuery(new Term("id", createFieldValue(getRandomParentId())));
   }
 
   private static int getRandomParentId() {
