diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStream.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStream.java
index b8352e9..f4695e2 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStream.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStream.java
@@ -20,6 +20,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
 import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.criteria.Subquery;
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStreamImpl.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStreamImpl.java
index bb8b4db..3723956 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStreamImpl.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/ExprStreamImpl.java
@@ -22,6 +22,7 @@ import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
@@ -210,14 +211,21 @@ class ExprStreamImpl<X, S extends Expression<X>> extends SearchStreamImpl<X, S>
         return (ExprStream<X, S>)super.fetch(attribute, joinType);
     }
 
+    @Override
+    public ExprStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType,
+      Function<? super Join<X, Y>, ? extends Expression<Boolean>> on) {
+        return (ExprStream<X, S>)super.fetch(attribute, joinType, on);
+    }
+
     @Override
     public ExprStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute) {
         return (ExprStream<X, S>)super.fetch(attribute);
     }
 
     @Override
-    public ExprStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute, JoinType joinType) {
-        return (ExprStream<X, S>)super.fetch(attribute, joinType);
+    public <E> ExprStream<X, S> fetch(PluralAttribute<? super X, ?, E> attribute, JoinType joinType,
+      Function<? super PluralJoin<X, ?, E>, ? extends Expression<Boolean>> on);
+        return (ExprStream<X, S>)super.fetch(attribute, joinType, on);
     }
 
 // Narrowing overrides (QueryStreamImpl)
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStream.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStream.java
index 191b167..ad2662a 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStream.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStream.java
@@ -20,6 +20,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.From;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
@@ -106,12 +107,20 @@ public interface FromStream<X, S extends From<?, X>> extends PathStream<X, S> {
     @Override
     FromStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType);
 
+    @Override
+    <Y> FromStream<X, S> fetch(SingularAttribute<? super X, Y> attribute, JoinType joinType,
+      Function<? super Join<X, Y>, ? extends Expression<Boolean>> on);
+
     @Override
     FromStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute);
 
     @Override
     FromStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute, JoinType joinType);
 
+    @Override
+    <E> FromStream<X, S> fetch(PluralAttribute<? super X, ?, E> attribute, JoinType joinType,
+      Function<? super PluralJoin<X, ?, E>, ? extends Expression<Boolean>> on);
+
 // Narrowing overrides (QueryStream)
 
     @Override
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStreamImpl.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStreamImpl.java
index daf35a3..b042ff3 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStreamImpl.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/FromStreamImpl.java
@@ -22,6 +22,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.From;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
@@ -230,13 +231,21 @@ class FromStreamImpl<X, S extends From<?, X>> extends PathStreamImpl<X, S> imple
 
     @Override
     public FromStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType) {
+        return this.fetch(attribute, joinType, join -> null);
+    }
+
+    @Override
+    public <Y> FromStream<X, S> fetch(SingularAttribute<? super X, Y> attribute, JoinType joinType,
+      Function<? super Join<X, Y>, ? extends Expression<Boolean>> on);
         if (attribute == null)
             throw new IllegalArgumentException("null attribute");
         if (joinType == null)
             throw new IllegalArgumentException("null joinType");
+        if (on == null)
+            throw new IllegalArgumentException("null on");
         return this.withConfig((builder, query) -> {
             final S selection = this.configure(builder, query);
-            selection.fetch(attribute, joinType);
+            final Fetch<X, Y> selection.fetch(attribute, joinType);
             return selection;
         });
     }
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStream.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStream.java
index 1a8412d..7da2f0e 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStream.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStream.java
@@ -20,6 +20,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
 import javax.persistence.criteria.Path;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStreamImpl.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStreamImpl.java
index a7287ee..24d9b9b 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStreamImpl.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/PathStreamImpl.java
@@ -22,6 +22,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
 import javax.persistence.criteria.Path;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStream.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStream.java
index f379f49..70e5f37 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStream.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStream.java
@@ -19,6 +19,7 @@ import javax.persistence.TemporalType;
 import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStreamImpl.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStreamImpl.java
index 16e7fe1..3ff0480 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStreamImpl.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/RootStreamImpl.java
@@ -21,6 +21,7 @@ import javax.persistence.criteria.AbstractQuery;
 import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStream.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStream.java
index 73d8302..47cfbed 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStream.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStream.java
@@ -872,7 +1094,20 @@ public interface SearchStream<X, S extends Selection<X>>
     SearchStream<X, S> fetch(SingularAttribute<? super X, ?> attribute);
 
     /**
-     * Add a singular fetch join to this stream.
+     * Add a singular fetch join to this stream using the specified join type.
+     *
+     * <p>
+     * Equivalent to {@link #fetch(SingularAttribute, JoinType, Function) fetch(attribute, JoinType.INNER, join -> null)}.
+     *
+     * @param attribute associated property
+     * @param joinType join type
+     * @return a new stream with specified fetch join
+     * @throws IllegalArgumentException if either parameter is null
+     */
+    SearchStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType);
+
+    /**
+     * Add a singular fetch join to this stream using the specified join type and ON condition.
      *
      * <p>
      * Unlike {@link #join(SingularAttribute) join()}, this method does not change the stream's content type.
@@ -881,10 +1116,12 @@ public interface SearchStream<X, S extends Selection<X>>
      *
      * @param attribute associated property
      * @param joinType join type
+     * @param on function returning a join ON condition, or returning null for none
      * @return a new stream with specified fetch join
-     * @throws IllegalArgumentException if either parameter is null
+     * @throws IllegalArgumentException if any parameter is null
      */
-    SearchStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType);
+    <Y> SearchStream<X, S> fetch(SingularAttribute<? super X, ?> attribute, JoinType joinType,
+      Function<? super Join<X, Y>, ? extends Expression<Boolean>> on);
 
     /**
      * Add a plural fetch join to this stream.
@@ -899,12 +1136,10 @@ public interface SearchStream<X, S extends Selection<X>>
     SearchStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute);
 
     /**
-     * Add a plural fetch join to this stream.
+     * Add a plural fetch join to this stream using the specified join type.
      *
      * <p>
-     * Unlike {@link #join(SingularAttribute) join()}, this method does not change the stream's content type.
-     * In other words, this method is used simply to pre-fetch an association, to avoid having to fetch it again
-     * later for each individual element in the stream.
+     * Equivalent to {@link #fetch(PluralAttribute, JoinType, Function) fetch(attribute, JoinType.INNER, join -> null)}.
      *
      * @param attribute associated property
      * @param joinType join type
@@ -913,6 +1148,23 @@ public interface SearchStream<X, S extends Selection<X>>
      */
     SearchStream<X, S> fetch(PluralAttribute<? super X, ?, ?> attribute, JoinType joinType);
 
+    /**
+     * Add a plural fetch join to this stream using the specified join type and ON condition.
+     *
+     * <p>
+     * Unlike {@link #join(PluralAttribute) join()}, this method does not change the stream's content type.
+     * In other words, this method is used simply to pre-fetch an association, to avoid having to fetch it again
+     * later for each individual element in the stream.
+     *
+     * @param attribute associated property
+     * @param joinType join type
+     * @param on function returning a join ON condition, or returning null for none
+     * @return a new stream with specified fetch join
+     * @throws IllegalArgumentException if any parameter is null
+     */
+    <E> SearchStream<X, S> fetch(PluralAttribute<? super X, ?, E> attribute, JoinType joinType,
+      Function<? super PluralJoin<X, ?, E>, ? extends Expression<Boolean>> on);
+
 // Narrowing overrides (QueryStream)
 
     @Override
diff --git a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStreamImpl.java b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStreamImpl.java
index 7d316c7..a4d14b3 100644
--- a/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStreamImpl.java
+++ b/querystream-jpa/src/main/java/org/dellroad/querystream/jpa/SearchStreamImpl.java
@@ -28,6 +28,7 @@ import javax.persistence.criteria.Expression;
 import javax.persistence.criteria.JoinType;
 import javax.persistence.criteria.Order;
 import javax.persistence.criteria.Path;
+import javax.persistence.criteria.PluralJoin;
 import javax.persistence.criteria.Root;
 import javax.persistence.criteria.Selection;
 import javax.persistence.metamodel.PluralAttribute;
