You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ro...@apache.org on 2025/12/08 16:04:29 UTC

(lucene) branch branch_10x updated: Deprecate SortField.setMissingValue() (#15483)

This is an automated email from the ASF dual-hosted git repository.

romseygeek pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new 202f206c2f9 Deprecate SortField.setMissingValue() (#15483)
202f206c2f9 is described below

commit 202f206c2f970adab65994f8f33fe0d7a7cda99a
Author: Alan Woodward <ro...@apache.org>
AuthorDate: Mon Dec 8 16:00:11 2025 +0000

    Deprecate SortField.setMissingValue() (#15483)
    
    Placeholder values for SortFields are set via setMissingValue(), after construction, which means
    that they are inherently mutable. This commit deprecates setMissingValue() and instead
    adds constructor parameters for missing values.
---
 lucene/CHANGES.txt                                 |   5 +-
 .../lucene70/Lucene70SegmentInfoFormat.java        |  21 +-
 .../org/apache/lucene/document/DoubleField.java    |  16 +-
 .../org/apache/lucene/document/FloatField.java     |  15 +-
 .../java/org/apache/lucene/document/IntField.java  |  15 +-
 .../org/apache/lucene/document/KeywordField.java   |  16 +-
 .../lucene/document/LatLonPointSortField.java      |   3 +-
 .../java/org/apache/lucene/document/LongField.java |  15 +-
 .../apache/lucene/document/XYPointSortField.java   |   3 +-
 .../apache/lucene/search/DoubleValuesSource.java   |  29 ++-
 .../org/apache/lucene/search/LongValuesSource.java |  29 ++-
 .../java/org/apache/lucene/search/SortField.java   |  59 +++--
 .../lucene/search/SortedNumericSortField.java      |  75 +++---
 .../apache/lucene/search/SortedSetSortField.java   |  22 +-
 .../document/TestLatLonPointDistanceSort.java      |   1 -
 .../org/apache/lucene/index/TestIndexSorting.java  | 251 ++++++++++++---------
 .../lucene/search/TestDoubleValuesSource.java      |   6 +-
 ...tIndexSortSortedNumericDocValuesRangeQuery.java |  42 ++--
 .../apache/lucene/search/TestLongValuesSource.java |   6 +-
 .../org/apache/lucene/search/TestSearchAfter.java  |  32 +--
 .../test/org/apache/lucene/search/TestSort.java    |  29 ++-
 .../apache/lucene/search/TestSortOptimization.java |  89 ++++----
 .../org/apache/lucene/search/TestSortRandom.java   |   6 +-
 .../lucene/search/TestSortedNumericSortField.java  |  10 +-
 .../lucene/search/TestSortedSetSelector.java       |  28 ++-
 .../lucene/search/TestSortedSetSortField.java      |   8 +-
 .../lucene/search/TestXYPointDistanceSort.java     |   1 -
 .../search/comparators/TestNumericComparator.java  |   8 +-
 .../valuesource/MultiValuedDoubleFieldSource.java  |   9 +-
 .../valuesource/MultiValuedFloatFieldSource.java   |  10 +-
 .../valuesource/MultiValuedIntFieldSource.java     |  10 +-
 .../valuesource/MultiValuedLongFieldSource.java    |  10 +-
 .../spatial3d/Geo3DPointOutsideSortField.java      |   3 +-
 .../lucene/spatial3d/Geo3DPointSortField.java      |   3 +-
 .../tests/index/BaseSegmentInfoFormatTestCase.java | 151 ++++++-------
 35 files changed, 614 insertions(+), 422 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index eb00dcbf05d..ac30a603505 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -26,7 +26,10 @@ API Changes
 * GITHUB#15422: Add a new method in DirectWriter to compute how many bytes are written for encoding a number of values using a number of
   bits per value. (Ignacio Vera)
 
-* GITHUB##15428: Add support for ExecutorServices to be passed into DirectoryReader.open() and DirectoryReader.openIfChanged() APIs (Bryce Kane)
+* GITHUB#15428: Add support for ExecutorServices to be passed into DirectoryReader.open() and DirectoryReader.openIfChanged() APIs (Bryce Kane)
+
+* GITHUB#15483: Deprecate SortField.setMissingValue() in preparation for making SortField immutable
+  in Lucene 11.0. Missing values can be set as constructor parameters.  (Alan Woodward)
 
 New Features
 ---------------------
diff --git a/lucene/backward-codecs/src/java/org/apache/lucene/backward_codecs/lucene70/Lucene70SegmentInfoFormat.java b/lucene/backward-codecs/src/java/org/apache/lucene/backward_codecs/lucene70/Lucene70SegmentInfoFormat.java
index 7f9fac4deed..50176c83ab3 100644
--- a/lucene/backward-codecs/src/java/org/apache/lucene/backward_codecs/lucene70/Lucene70SegmentInfoFormat.java
+++ b/lucene/backward-codecs/src/java/org/apache/lucene/backward_codecs/lucene70/Lucene70SegmentInfoFormat.java
@@ -231,15 +231,6 @@ public class Lucene70SegmentInfoFormat extends SegmentInfoFormat {
           throw new CorruptIndexException("invalid index sort reverse: " + b, input);
         }
 
-        if (sortedSetSelector != null) {
-          sortFields[i] = new SortedSetSortField(fieldName, reverse, sortedSetSelector);
-        } else if (sortedNumericSelector != null) {
-          sortFields[i] =
-              new SortedNumericSortField(fieldName, sortType, reverse, sortedNumericSelector);
-        } else {
-          sortFields[i] = new SortField(fieldName, sortType, reverse);
-        }
-
         Object missingValue;
         b = input.readByte();
         if (b == 0) {
@@ -288,8 +279,16 @@ public class Lucene70SegmentInfoFormat extends SegmentInfoFormat {
               throw new AssertionError("unhandled sortType=" + sortType);
           }
         }
-        if (missingValue != null) {
-          sortFields[i].setMissingValue(missingValue);
+
+        if (sortedSetSelector != null) {
+          sortFields[i] =
+              new SortedSetSortField(fieldName, reverse, sortedSetSelector, missingValue);
+        } else if (sortedNumericSelector != null) {
+          sortFields[i] =
+              new SortedNumericSortField(
+                  fieldName, sortType, reverse, sortedNumericSelector, missingValue);
+        } else {
+          sortFields[i] = new SortField(fieldName, sortType, reverse);
         }
       }
       indexSort = new Sort(sortFields);
diff --git a/lucene/core/src/java/org/apache/lucene/document/DoubleField.java b/lucene/core/src/java/org/apache/lucene/document/DoubleField.java
index 8eaf8852463..4771e5c150e 100644
--- a/lucene/core/src/java/org/apache/lucene/document/DoubleField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/DoubleField.java
@@ -179,6 +179,20 @@ public final class DoubleField extends Field {
    */
   public static SortField newSortField(
       String field, boolean reverse, SortedNumericSelector.Type selector) {
-    return new SortedNumericSortField(field, SortField.Type.DOUBLE, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.DOUBLE, reverse, selector, null);
+  }
+
+  /**
+   * Create a new {@link SortField} for double values.
+   *
+   * @param field field name. must not be {@code null}.
+   * @param reverse true if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   * @param missingValue a sort value to use for documents with no value in the field
+   */
+  public static SortField newSortField(
+      String field, boolean reverse, SortedNumericSelector.Type selector, double missingValue) {
+    return new SortedNumericSortField(
+        field, SortField.Type.DOUBLE, reverse, selector, missingValue);
   }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/document/FloatField.java b/lucene/core/src/java/org/apache/lucene/document/FloatField.java
index 12eb131cb0f..e7b475eb0ec 100644
--- a/lucene/core/src/java/org/apache/lucene/document/FloatField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/FloatField.java
@@ -179,6 +179,19 @@ public final class FloatField extends Field {
    */
   public static SortField newSortField(
       String field, boolean reverse, SortedNumericSelector.Type selector) {
-    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector, null);
+  }
+
+  /**
+   * Create a new {@link SortField} for float values.
+   *
+   * @param field field name. must not be {@code null}.
+   * @param reverse true if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   * @param missingValue a sort value to use for documents with no value in the field
+   */
+  public static SortField newSortField(
+      String field, boolean reverse, SortedNumericSelector.Type selector, float missingValue) {
+    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector, missingValue);
   }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/document/IntField.java b/lucene/core/src/java/org/apache/lucene/document/IntField.java
index b6a3493e104..fc03d1a6de5 100644
--- a/lucene/core/src/java/org/apache/lucene/document/IntField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/IntField.java
@@ -172,6 +172,19 @@ public final class IntField extends Field {
    */
   public static SortField newSortField(
       String field, boolean reverse, SortedNumericSelector.Type selector) {
-    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector, null);
+  }
+
+  /**
+   * Create a new {@link SortField} for int values.
+   *
+   * @param field field name. must not be {@code null}.
+   * @param reverse true if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   * @param missingValue a sort value to use for documents with no value in the field
+   */
+  public static SortField newSortField(
+      String field, boolean reverse, SortedNumericSelector.Type selector, int missingValue) {
+    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector, missingValue);
   }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/document/KeywordField.java b/lucene/core/src/java/org/apache/lucene/document/KeywordField.java
index 2ed22b2e141..bc61550bd39 100644
--- a/lucene/core/src/java/org/apache/lucene/document/KeywordField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/KeywordField.java
@@ -178,6 +178,20 @@ public class KeywordField extends Field {
         MultiTermQuery.CONSTANT_SCORE_BLENDED_REWRITE, field, values);
   }
 
+  /**
+   * Create a new {@link SortField} for {@link BytesRef} values.
+   *
+   * @param field field name. must not be {@code null}.
+   * @param reverse true if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   */
+  public static SortField newSortField(
+      String field, boolean reverse, SortedSetSelector.Type selector, Object missingValue) {
+    Objects.requireNonNull(field, "field must not be null");
+    Objects.requireNonNull(selector, "selector must not be null");
+    return new SortedSetSortField(field, reverse, selector, missingValue);
+  }
+
   /**
    * Create a new {@link SortField} for {@link BytesRef} values.
    *
@@ -189,6 +203,6 @@ public class KeywordField extends Field {
       String field, boolean reverse, SortedSetSelector.Type selector) {
     Objects.requireNonNull(field, "field must not be null");
     Objects.requireNonNull(selector, "selector must not be null");
-    return new SortedSetSortField(field, reverse, selector);
+    return new SortedSetSortField(field, reverse, selector, null);
   }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
index 9c4c0e8f861..e8e0ee36a45 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointSortField.java
@@ -27,7 +27,7 @@ final class LatLonPointSortField extends SortField {
   final double longitude;
 
   LatLonPointSortField(String field, double latitude, double longitude) {
-    super(field, SortField.Type.CUSTOM);
+    super(field, SortField.Type.CUSTOM, false, Double.POSITIVE_INFINITY);
     if (field == null) {
       throw new IllegalArgumentException("field must not be null");
     }
@@ -35,7 +35,6 @@ final class LatLonPointSortField extends SortField {
     GeoUtils.checkLongitude(longitude);
     this.latitude = latitude;
     this.longitude = longitude;
-    setMissingValue(Double.POSITIVE_INFINITY);
   }
 
   @Override
diff --git a/lucene/core/src/java/org/apache/lucene/document/LongField.java b/lucene/core/src/java/org/apache/lucene/document/LongField.java
index 65fadfd4124..568d9d6bef6 100644
--- a/lucene/core/src/java/org/apache/lucene/document/LongField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/LongField.java
@@ -172,7 +172,20 @@ public final class LongField extends Field {
    */
   public static SortField newSortField(
       String field, boolean reverse, SortedNumericSelector.Type selector) {
-    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector, null);
+  }
+
+  /**
+   * Create a new {@link SortField} for long values.
+   *
+   * @param field field name. must not be {@code null}.
+   * @param reverse true if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   * @param missingValue a sort value to use for documents with no value in the field
+   */
+  public static SortField newSortField(
+      String field, boolean reverse, SortedNumericSelector.Type selector, long missingValue) {
+    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector, missingValue);
   }
 
   /**
diff --git a/lucene/core/src/java/org/apache/lucene/document/XYPointSortField.java b/lucene/core/src/java/org/apache/lucene/document/XYPointSortField.java
index f5727888ab8..2c8c8b4a005 100644
--- a/lucene/core/src/java/org/apache/lucene/document/XYPointSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/XYPointSortField.java
@@ -26,13 +26,12 @@ final class XYPointSortField extends SortField {
   final float y;
 
   XYPointSortField(String field, float x, float y) {
-    super(field, Type.CUSTOM);
+    super(field, Type.CUSTOM, false, Double.POSITIVE_INFINITY);
     if (field == null) {
       throw new IllegalArgumentException("field must not be null");
     }
     this.x = x;
     this.y = y;
-    setMissingValue(Double.POSITIVE_INFINITY);
   }
 
   @Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
index 903c1e9a8b6..e6a55316f00 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
@@ -98,7 +98,17 @@ public abstract class DoubleValuesSource implements SegmentCacheable {
    * @param reverse true if the sort should be decreasing
    */
   public SortField getSortField(boolean reverse) {
-    return new DoubleValuesSortField(this, reverse);
+    return new DoubleValuesSortField(this, reverse, 0);
+  }
+
+  /**
+   * Create a sort field based on the value of this producer
+   *
+   * @param reverse true if the sort should be decreasing
+   * @param missingValue a placeholder to use for documents with no value
+   */
+  public SortField getSortField(boolean reverse, double missingValue) {
+    return new DoubleValuesSortField(this, reverse, missingValue);
   }
 
   @Override
@@ -514,8 +524,8 @@ public abstract class DoubleValuesSource implements SegmentCacheable {
 
     final DoubleValuesSource producer;
 
-    DoubleValuesSortField(DoubleValuesSource producer, boolean reverse) {
-      super(producer.toString(), new DoubleValuesComparatorSource(producer), reverse);
+    DoubleValuesSortField(DoubleValuesSource producer, boolean reverse, double missingValue) {
+      super(producer.toString(), new DoubleValuesComparatorSource(producer, missingValue), reverse);
       this.producer = producer;
     }
 
@@ -549,11 +559,10 @@ public abstract class DoubleValuesSource implements SegmentCacheable {
       if (rewrittenSource == producer) {
         return this;
       }
-      DoubleValuesSortField rewritten = new DoubleValuesSortField(rewrittenSource, reverse);
-      if (missingValue != null) {
-        rewritten.setMissingValue(missingValue);
-      }
-      return rewritten;
+      return new DoubleValuesSortField(
+          rewrittenSource,
+          reverse,
+          ((DoubleValuesComparatorSource) getComparatorSource()).missingValue);
     }
   }
 
@@ -565,9 +574,9 @@ public abstract class DoubleValuesSource implements SegmentCacheable {
     private final DoubleValuesSource producer;
     private double missingValue;
 
-    DoubleValuesComparatorSource(DoubleValuesSource producer) {
+    DoubleValuesComparatorSource(DoubleValuesSource producer, double missingValue) {
       this.producer = producer;
-      this.missingValue = 0d;
+      this.missingValue = missingValue;
     }
 
     void setMissingValue(double missingValue) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
index c6756ff6763..86f4f4daff6 100644
--- a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
@@ -75,7 +75,17 @@ public abstract class LongValuesSource implements SegmentCacheable {
    * @param reverse true if the sort should be decreasing
    */
   public SortField getSortField(boolean reverse) {
-    return new LongValuesSortField(this, reverse);
+    return new LongValuesSortField(this, reverse, 0);
+  }
+
+  /**
+   * Create a sort field based on the value of this producer
+   *
+   * @param reverse true if the sort should be decreasing
+   * @param missingValue a placeholder to use for documents with no value
+   */
+  public SortField getSortField(boolean reverse, long missingValue) {
+    return new LongValuesSortField(this, reverse, missingValue);
   }
 
   /** Convert to a DoubleValuesSource by casting long values to doubles */
@@ -275,8 +285,8 @@ public abstract class LongValuesSource implements SegmentCacheable {
 
     final LongValuesSource producer;
 
-    public LongValuesSortField(LongValuesSource producer, boolean reverse) {
-      super(producer.toString(), new LongValuesComparatorSource(producer), reverse);
+    public LongValuesSortField(LongValuesSource producer, boolean reverse, long missingValue) {
+      super(producer.toString(), new LongValuesComparatorSource(producer, missingValue), reverse);
       this.producer = producer;
     }
 
@@ -310,11 +320,10 @@ public abstract class LongValuesSource implements SegmentCacheable {
       if (producer == rewrittenSource) {
         return this;
       }
-      LongValuesSortField rewritten = new LongValuesSortField(rewrittenSource, reverse);
-      if (missingValue != null) {
-        rewritten.setMissingValue(missingValue);
-      }
-      return rewritten;
+      return new LongValuesSortField(
+          rewrittenSource,
+          reverse,
+          ((LongValuesComparatorSource) getComparatorSource()).missingValue);
     }
   }
 
@@ -326,9 +335,9 @@ public abstract class LongValuesSource implements SegmentCacheable {
     private final LongValuesSource producer;
     private long missingValue;
 
-    public LongValuesComparatorSource(LongValuesSource producer) {
+    public LongValuesComparatorSource(LongValuesSource producer, long missingValue) {
       this.producer = producer;
-      this.missingValue = 0L;
+      this.missingValue = missingValue;
     }
 
     void setMissingValue(long missingValue) {
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortField.java b/lucene/core/src/java/org/apache/lucene/search/SortField.java
index 4f35429f3c7..9cb7f89c155 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortField.java
@@ -146,7 +146,18 @@ public class SortField {
    * @param type Type of values in the terms.
    */
   public SortField(String field, Type type) {
-    this(field, type, false);
+    this(field, type, false, null);
+  }
+
+  /**
+   * Creates a sort by terms in the given field with the type of term values explicitly given.
+   *
+   * @param field Name of field to sort by. Can be <code>null</code> if <code>type</code> is SCORE
+   *     or DOC.
+   * @param type Type of values in the terms.
+   */
+  public SortField(String field, Type type, boolean reverse) {
+    this(field, type, reverse, null);
   }
 
   /**
@@ -158,11 +169,12 @@ public class SortField {
    * @param type Type of values in the terms.
    * @param reverse True if natural order should be reversed.
    */
-  public SortField(String field, Type type, boolean reverse) {
+  public SortField(String field, Type type, boolean reverse, Object missingValue) {
     this.field = field;
     this.type = type;
     this.reverse = reverse;
-    validateField(field, type);
+    this.missingValue = missingValue;
+    validateField(field, type, missingValue);
   }
 
   /**
@@ -187,7 +199,8 @@ public class SortField {
     this.type = Type.CUSTOM;
     this.reverse = reverse;
     this.comparatorSource = comparator;
-    validateField(field, type);
+    this.missingValue = null; // missingValue factored into comparator source
+    validateField(field, type, null);
   }
 
   /** A SortFieldProvider for field sorts */
@@ -203,40 +216,39 @@ public class SortField {
 
     @Override
     public SortField readSortField(DataInput in) throws IOException {
-      SortField sf = new SortField(in.readString(), readType(in), in.readInt() == 1);
+      String field = in.readString();
+      Type type = readType(in);
+      boolean reverse = in.readInt() == 1;
       if (in.readInt() == 1) {
         // missing object
-        switch (sf.type) {
+        switch (type) {
           case STRING:
             int missingString = in.readInt();
             if (missingString == 1) {
-              sf.setMissingValue(STRING_FIRST);
+              return new SortField(field, type, reverse, STRING_FIRST);
             } else {
-              sf.setMissingValue(STRING_LAST);
+              return new SortField(field, type, reverse, STRING_LAST);
             }
-            break;
           case INT:
-            sf.setMissingValue(in.readInt());
-            break;
+            return new SortField(field, type, reverse, in.readInt());
           case LONG:
-            sf.setMissingValue(in.readLong());
-            break;
+            return new SortField(field, type, reverse, in.readLong());
           case FLOAT:
-            sf.setMissingValue(NumericUtils.sortableIntToFloat(in.readInt()));
-            break;
+            return new SortField(
+                field, type, reverse, NumericUtils.sortableIntToFloat(in.readInt()));
           case DOUBLE:
-            sf.setMissingValue(NumericUtils.sortableLongToDouble(in.readLong()));
-            break;
+            return new SortField(
+                field, type, reverse, NumericUtils.sortableLongToDouble(in.readLong()));
           case CUSTOM:
           case DOC:
           case REWRITEABLE:
           case STRING_VAL:
           case SCORE:
           default:
-            throw new IllegalArgumentException("Cannot deserialize sort of type " + sf.type);
+            throw new IllegalArgumentException("Cannot deserialize sort of type " + type);
         }
       }
-      return sf;
+      return new SortField(field, type, reverse, null);
     }
 
     @Override
@@ -323,6 +335,7 @@ public class SortField {
   }
 
   /** Set the value to use for documents that don't have a value. */
+  @Deprecated
   public void setMissingValue(Object missingValue) {
     if (type == Type.STRING || type == Type.STRING_VAL) {
       if (missingValue != STRING_FIRST && missingValue != STRING_LAST) {
@@ -357,12 +370,18 @@ public class SortField {
 
   // Sets field & type, and ensures field is not NULL unless
   // type is SCORE or DOC
-  private void validateField(String field, Type type) {
+  private void validateField(String field, Type type, Object missingValue) {
     if (field == null) {
       if (type != Type.SCORE && type != Type.DOC) {
         throw new IllegalArgumentException("field can only be null when type is SCORE or DOC");
       }
     }
+    if (type == Type.STRING) {
+      if (missingValue != null && missingValue != STRING_FIRST && missingValue != STRING_LAST) {
+        throw new IllegalArgumentException(
+            "For Type.STRING, missing value must be either STRING_FIRST or STRING_LAST");
+      }
+    }
   }
 
   /**
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java b/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
index 614df387d98..345a1680510 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortedNumericSortField.java
@@ -69,7 +69,7 @@ public class SortedNumericSortField extends SortField {
    * @param reverse True if natural order should be reversed.
    */
   public SortedNumericSortField(String field, SortField.Type type, boolean reverse) {
-    this(field, type, reverse, SortedNumericSelector.Type.MIN);
+    this(field, type, reverse, SortedNumericSelector.Type.MIN, null);
   }
 
   /**
@@ -83,7 +83,26 @@ public class SortedNumericSortField extends SortField {
    */
   public SortedNumericSortField(
       String field, SortField.Type type, boolean reverse, SortedNumericSelector.Type selector) {
-    super(field, SortField.Type.CUSTOM, reverse);
+    this(field, type, reverse, selector, null);
+  }
+
+  /**
+   * Creates a sort, possibly in reverse, specifying how the sort value from the document's set is
+   * selected.
+   *
+   * @param field Name of field to sort by. Must not be null.
+   * @param type Type of values
+   * @param reverse True if natural order should be reversed.
+   * @param selector custom selector type for choosing the sort value from the set.
+   * @param missingValue the sort value to use for documents with no value in the field
+   */
+  public SortedNumericSortField(
+      String field,
+      SortField.Type type,
+      boolean reverse,
+      SortedNumericSelector.Type selector,
+      Object missingValue) {
+    super(field, SortField.Type.CUSTOM, reverse, missingValue);
     if (selector == null) {
       throw new NullPointerException();
     }
@@ -107,34 +126,34 @@ public class SortedNumericSortField extends SortField {
 
     @Override
     public SortField readSortField(DataInput in) throws IOException {
-      SortedNumericSortField sf =
-          new SortedNumericSortField(
-              in.readString(), readType(in), in.readInt() == 1, readSelectorType(in));
+      String field = in.readString();
+      Type type = readType(in);
+      boolean reverse = in.readInt() == 1;
+      SortedNumericSelector.Type selectorType = readSelectorType(in);
       if (in.readInt() == 1) {
-        switch (sf.type) {
-          case INT:
-            sf.setMissingValue(in.readInt());
-            break;
-          case LONG:
-            sf.setMissingValue(in.readLong());
-            break;
-          case FLOAT:
-            sf.setMissingValue(NumericUtils.sortableIntToFloat(in.readInt()));
-            break;
-          case DOUBLE:
-            sf.setMissingValue(NumericUtils.sortableLongToDouble(in.readLong()));
-            break;
-          case CUSTOM:
-          case DOC:
-          case REWRITEABLE:
-          case STRING_VAL:
-          case SCORE:
-          case STRING:
-          default:
-            throw new AssertionError();
-        }
+        return switch (type) {
+          case INT -> new SortedNumericSortField(field, type, reverse, selectorType, in.readInt());
+          case LONG ->
+              new SortedNumericSortField(field, type, reverse, selectorType, in.readLong());
+          case FLOAT ->
+              new SortedNumericSortField(
+                  field,
+                  type,
+                  reverse,
+                  selectorType,
+                  NumericUtils.sortableIntToFloat(in.readInt()));
+          case DOUBLE ->
+              new SortedNumericSortField(
+                  field,
+                  type,
+                  reverse,
+                  selectorType,
+                  NumericUtils.sortableLongToDouble(in.readLong()));
+          // $CASES-OMITTED$
+          default -> throw new AssertionError();
+        };
       }
-      return sf;
+      return new SortedNumericSortField(field, type, reverse, selectorType, null);
     }
 
     @Override
diff --git a/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java b/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
index f170d94a62c..3a2acbcd571 100644
--- a/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
+++ b/lucene/core/src/java/org/apache/lucene/search/SortedSetSortField.java
@@ -54,7 +54,11 @@ public class SortedSetSortField extends SortField {
    * @param reverse True if natural order should be reversed.
    */
   public SortedSetSortField(String field, boolean reverse) {
-    this(field, reverse, SortedSetSelector.Type.MIN);
+    this(field, reverse, SortedSetSelector.Type.MIN, null);
+  }
+
+  public SortedSetSortField(String field, boolean reverse, SortedSetSelector.Type selector) {
+    this(field, reverse, selector, null);
   }
 
   /**
@@ -67,8 +71,9 @@ public class SortedSetSortField extends SortField {
    *     <p>NOTE: selectors other than {@link SortedSetSelector.Type#MIN} require optional codec
    *     support.
    */
-  public SortedSetSortField(String field, boolean reverse, SortedSetSelector.Type selector) {
-    super(field, SortField.Type.CUSTOM, reverse);
+  public SortedSetSortField(
+      String field, boolean reverse, SortedSetSelector.Type selector, Object missingValue) {
+    super(field, SortField.Type.CUSTOM, reverse, missingValue);
     if (selector == null) {
       throw new NullPointerException();
     }
@@ -88,15 +93,16 @@ public class SortedSetSortField extends SortField {
 
     @Override
     public SortField readSortField(DataInput in) throws IOException {
-      SortField sf =
-          new SortedSetSortField(in.readString(), in.readInt() == 1, readSelectorType(in));
+      String field = in.readString();
+      boolean reverse = in.readInt() == 1;
+      SortedSetSelector.Type type = readSelectorType(in);
       int missingValue = in.readInt();
       if (missingValue == 1) {
-        sf.setMissingValue(SortField.STRING_FIRST);
+        return new SortedSetSortField(field, reverse, type, SortField.STRING_FIRST);
       } else if (missingValue == 2) {
-        sf.setMissingValue(SortField.STRING_LAST);
+        return new SortedSetSortField(field, reverse, type, SortField.STRING_LAST);
       }
-      return sf;
+      return new SortedSetSortField(field, reverse, type, null);
     }
 
     @Override
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
index f114aac8594..0cd0d58208b 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestLatLonPointDistanceSort.java
@@ -238,7 +238,6 @@ public class TestLatLonPointDistanceSort extends LuceneTestCase {
       int topN = TestUtil.nextInt(random(), 1, reader.maxDoc());
       // sort by distance, then ID
       SortField distanceSort = LatLonDocValuesField.newDistanceSort("field", lat, lon);
-      distanceSort.setMissingValue(missingValue);
       Sort sort = new Sort(distanceSort, new SortField("id", SortField.Type.INT));
 
       TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), topN, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java b/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
index 91a16792617..8193d18ec1b 100644
--- a/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
+++ b/lucene/core/src/test/org/apache/lucene/index/TestIndexSorting.java
@@ -70,7 +70,9 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
 import org.apache.lucene.search.SortedNumericSortField;
+import org.apache.lucene.search.SortedSetSelector;
 import org.apache.lucene.search.SortedSetSortField;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TermStatistics;
@@ -371,8 +373,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.STRING, reverse);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+      SortField sortField =
+          new SortField("foo", SortField.Type.STRING, reverse, SortField.STRING_FIRST);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -418,8 +420,9 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedSetSortField("foo", reverse);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+      SortField sortField =
+          new SortedSetSortField(
+              "foo", reverse, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -475,8 +478,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.STRING, reverse);
-      sortField.setMissingValue(SortField.STRING_LAST);
+      SortField sortField =
+          new SortField("foo", SortField.Type.STRING, reverse, SortField.STRING_LAST);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -522,8 +525,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedSetSortField("foo", reverse);
-      sortField.setMissingValue(SortField.STRING_LAST);
+      SortField sortField =
+          new SortedSetSortField("foo", reverse, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -659,8 +662,7 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.LONG, reverse);
-      sortField.setMissingValue(Long.MIN_VALUE);
+      SortField sortField = new SortField("foo", SortField.Type.LONG, reverse, Long.MIN_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -706,8 +708,9 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.LONG, reverse);
-      sortField.setMissingValue(Long.MIN_VALUE);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo", SortField.Type.LONG, reverse, SortedNumericSelector.Type.MIN, Long.MIN_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -762,8 +765,7 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.LONG, reverse);
-      sortField.setMissingValue(Long.MAX_VALUE);
+      SortField sortField = new SortField("foo", SortField.Type.LONG, reverse, Long.MAX_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -810,8 +812,9 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.LONG, reverse);
-      sortField.setMissingValue(Long.MAX_VALUE);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo", SortField.Type.LONG, reverse, SortedNumericSelector.Type.MIN, Long.MAX_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -950,8 +953,7 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.INT, reverse);
-      sortField.setMissingValue(Integer.MIN_VALUE);
+      SortField sortField = new SortField("foo", SortField.Type.INT, reverse, Integer.MIN_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -996,8 +998,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.INT, reverse);
-      sortField.setMissingValue(Integer.MIN_VALUE);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.INT,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Integer.MIN_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1052,8 +1059,7 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.INT, reverse);
-      sortField.setMissingValue(Integer.MAX_VALUE);
+      SortField sortField = new SortField("foo", SortField.Type.INT, reverse, Integer.MAX_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1100,8 +1106,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.INT, reverse);
-      sortField.setMissingValue(Integer.MAX_VALUE);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.INT,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Integer.MAX_VALUE);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1238,8 +1249,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.DOUBLE, reverse);
-      sortField.setMissingValue(Double.NEGATIVE_INFINITY);
+      SortField sortField =
+          new SortField("foo", SortField.Type.DOUBLE, reverse, Double.NEGATIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1284,8 +1295,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.DOUBLE, reverse);
-      sortField.setMissingValue(Double.NEGATIVE_INFINITY);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.DOUBLE,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Double.NEGATIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1340,8 +1356,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.DOUBLE, reverse);
-      sortField.setMissingValue(Double.POSITIVE_INFINITY);
+      SortField sortField =
+          new SortField("foo", SortField.Type.DOUBLE, reverse, Double.POSITIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1387,8 +1403,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.DOUBLE, reverse);
-      sortField.setMissingValue(Double.POSITIVE_INFINITY);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.DOUBLE,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Double.POSITIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1524,8 +1545,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.FLOAT, reverse);
-      sortField.setMissingValue(Float.NEGATIVE_INFINITY);
+      SortField sortField =
+          new SortField("foo", SortField.Type.FLOAT, reverse, Float.NEGATIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1570,8 +1591,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.FLOAT, reverse);
-      sortField.setMissingValue(Float.NEGATIVE_INFINITY);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.FLOAT,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Float.NEGATIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1626,8 +1652,8 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortField("foo", SortField.Type.FLOAT, reverse);
-      sortField.setMissingValue(Float.POSITIVE_INFINITY);
+      SortField sortField =
+          new SortField("foo", SortField.Type.FLOAT, reverse, Float.POSITIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -1673,8 +1699,13 @@ public class TestIndexSorting extends LuceneTestCase {
     for (boolean reverse : new boolean[] {true, false}) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("foo", SortField.Type.FLOAT, reverse);
-      sortField.setMissingValue(Float.POSITIVE_INFINITY);
+      SortField sortField =
+          new SortedNumericSortField(
+              "foo",
+              SortField.Type.FLOAT,
+              reverse,
+              SortedNumericSelector.Type.MIN,
+              Float.POSITIVE_INFINITY);
       Sort indexSort = new Sort(sortField);
       iwc.setIndexSort(indexSort);
       IndexWriter w = new IndexWriter(dir, iwc);
@@ -2456,75 +2487,76 @@ public class TestIndexSorting extends LuceneTestCase {
 
   private static SortField randomIndexSortField() {
     boolean reversed = random().nextBoolean();
-    SortField sortField;
-    switch (random().nextInt(10)) {
-      case 0:
-        sortField = new SortField("int", SortField.Type.INT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextInt());
-        }
-        break;
-      case 1:
-        sortField = new SortedNumericSortField("multi_valued_int", SortField.Type.INT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextInt());
-        }
-        break;
-      case 2:
-        sortField = new SortField("long", SortField.Type.LONG, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextLong());
-        }
-        break;
-      case 3:
-        sortField = new SortedNumericSortField("multi_valued_long", SortField.Type.LONG, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextLong());
-        }
-        break;
-      case 4:
-        sortField = new SortField("float", SortField.Type.FLOAT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextFloat());
-        }
-        break;
-      case 5:
-        sortField =
-            new SortedNumericSortField("multi_valued_float", SortField.Type.FLOAT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextFloat());
-        }
-        break;
-      case 6:
-        sortField = new SortField("double", SortField.Type.DOUBLE, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextDouble());
-        }
-        break;
-      case 7:
-        sortField =
-            new SortedNumericSortField("multi_valued_double", SortField.Type.DOUBLE, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextDouble());
-        }
-        break;
-      case 8:
-        sortField = new SortField("bytes", SortField.Type.STRING, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(SortField.STRING_LAST);
-        }
-        break;
-      case 9:
-        sortField = new SortedSetSortField("multi_valued_bytes", reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(SortField.STRING_LAST);
-        }
-        break;
-      default:
-        sortField = null;
+    return switch (random().nextInt(10)) {
+      case 0 ->
+          new SortField(
+              "int",
+              SortField.Type.INT,
+              reversed,
+              random().nextBoolean() ? random().nextInt() : null);
+      case 1 ->
+          new SortedNumericSortField(
+              "multi_valued_int",
+              SortField.Type.INT,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextInt() : null);
+      case 2 ->
+          new SortField(
+              "long",
+              SortField.Type.LONG,
+              reversed,
+              random().nextBoolean() ? random().nextLong() : null);
+      case 3 ->
+          new SortedNumericSortField(
+              "multi_valued_long",
+              SortField.Type.LONG,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextLong() : null);
+      case 4 ->
+          new SortField(
+              "float",
+              SortField.Type.FLOAT,
+              reversed,
+              random().nextBoolean() ? random().nextFloat() : null);
+      case 5 ->
+          new SortedNumericSortField(
+              "multi_valued_float",
+              SortField.Type.FLOAT,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextFloat() : null);
+      case 6 ->
+          new SortField(
+              "double",
+              SortField.Type.DOUBLE,
+              reversed,
+              random().nextBoolean() ? random().nextDouble() : null);
+      case 7 ->
+          new SortedNumericSortField(
+              "multi_valued_double",
+              SortField.Type.DOUBLE,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextDouble() : null);
+      case 8 ->
+          new SortField(
+              "bytes",
+              SortField.Type.STRING,
+              reversed,
+              random().nextBoolean() ? SortField.STRING_LAST : null);
+      case 9 ->
+          new SortedSetSortField(
+              "multi_valued_bytes",
+              reversed,
+              SortedSetSelector.Type.MIN,
+              random().nextBoolean() ? SortField.STRING_LAST : null);
+      default -> {
         fail();
-    }
-    return sortField;
+        yield null;
+      }
+    };
   }
 
   private static Sort randomSort() {
@@ -2773,8 +2805,7 @@ public class TestIndexSorting extends LuceneTestCase {
   public void testIndexSortOnSparseField() throws Exception {
     Directory dir = newDirectory();
     IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-    SortField sortField = new SortField("sparse", SortField.Type.INT, false);
-    sortField.setMissingValue(Integer.MIN_VALUE);
+    SortField sortField = new SortField("sparse", SortField.Type.INT, false, Integer.MIN_VALUE);
     Sort indexSort = new Sort(sortField);
     iwc.setIndexSort(indexSort);
     IndexWriter w = new IndexWriter(dir, iwc);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java b/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
index f7242d5cfb2..59eacfd0589 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
@@ -105,16 +105,14 @@ public class TestDoubleValuesSource extends LuceneTestCase {
     DoubleValuesSource onefield = DoubleValuesSource.fromDoubleField("onefield");
 
     // sort decreasing, missing last
-    SortField oneFieldSort = onefield.getSortField(true);
-    oneFieldSort.setMissingValue(Double.MIN_VALUE);
+    SortField oneFieldSort = onefield.getSortField(true, Double.MIN_VALUE);
 
     TopDocs results = searcher.search(new MatchAllDocsQuery(), 1, new Sort(oneFieldSort));
     FieldDoc first = (FieldDoc) results.scoreDocs[0];
     assertEquals(LEAST_DOUBLE_VALUE, first.fields[0]);
 
     // sort increasing, missing last
-    oneFieldSort = onefield.getSortField(false);
-    oneFieldSort.setMissingValue(Double.MAX_VALUE);
+    oneFieldSort = onefield.getSortField(false, Double.MAX_VALUE);
 
     results = searcher.search(new MatchAllDocsQuery(), 1, new Sort(oneFieldSort));
     first = (FieldDoc) results.scoreDocs[0];
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestIndexSortSortedNumericDocValuesRangeQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestIndexSortSortedNumericDocValuesRangeQuery.java
index 631f35abbe2..b69d5b03b1c 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestIndexSortSortedNumericDocValuesRangeQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestIndexSortSortedNumericDocValuesRangeQuery.java
@@ -49,15 +49,17 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
 
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
       boolean reverse = random().nextBoolean();
-      SortField sortField = new SortedNumericSortField("dv", SortField.Type.LONG, reverse);
       boolean enableMissingValue = random().nextBoolean();
+      Long missingValue = null;
       if (enableMissingValue) {
-        long missingValue =
+        missingValue =
             random().nextBoolean()
                 ? TestUtil.nextLong(random(), -100, 10000)
                 : (random().nextBoolean() ? Long.MIN_VALUE : Long.MAX_VALUE);
-        sortField.setMissingValue(missingValue);
       }
+      SortField sortField =
+          new SortedNumericSortField(
+              "dv", SortField.Type.LONG, reverse, SortedNumericSelector.Type.MIN, missingValue);
       iwc.setIndexSort(new Sort(sortField));
 
       RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwc);
@@ -102,15 +104,17 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
 
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
       boolean reverse = random().nextBoolean();
-      SortField sortField = new SortedNumericSortField("dv", SortField.Type.INT, reverse);
       boolean enableMissingValue = random().nextBoolean();
+      Integer missingValue = null;
       if (enableMissingValue) {
-        int missingValue =
+        missingValue =
             random().nextBoolean()
                 ? TestUtil.nextInt(random(), -100, 10000)
                 : (random().nextBoolean() ? Integer.MIN_VALUE : Integer.MAX_VALUE);
-        sortField.setMissingValue(missingValue);
       }
+      SortField sortField =
+          new SortedNumericSortField(
+              "dv", SortField.Type.INT, reverse, SortedNumericSelector.Type.MIN, missingValue);
       iwc.setIndexSort(new Sort(sortField));
 
       RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwc);
@@ -344,8 +348,13 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
     Directory dir = newDirectory();
 
     IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-    SortField sortField = new SortedNumericSortField("field", SortField.Type.LONG);
-    sortField.setMissingValue(random().nextLong());
+    SortField sortField =
+        new SortedNumericSortField(
+            "field",
+            SortField.Type.LONG,
+            false,
+            SortedNumericSelector.Type.MIN,
+            random().nextLong());
     iwc.setIndexSort(new Sort(sortField));
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
 
@@ -545,15 +554,17 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
     for (int iter = 0; iter < iters; ++iter) {
       Directory dir = newDirectory();
       IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-      SortField sortField = new SortedNumericSortField("field", SortField.Type.LONG);
       boolean enableMissingValue = random().nextBoolean();
+      Long missingValue = null;
       if (enableMissingValue) {
-        long missingValue =
+        missingValue =
             random().nextBoolean()
                 ? TestUtil.nextLong(random(), -100, 10000)
                 : (random().nextBoolean() ? Long.MIN_VALUE : Long.MAX_VALUE);
-        sortField.setMissingValue(missingValue);
       }
+      SortField sortField =
+          new SortedNumericSortField(
+              "field", SortField.Type.LONG, false, SortedNumericSelector.Type.MIN, missingValue);
       iwc.setIndexSort(new Sort(sortField));
 
       RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
@@ -607,11 +618,16 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
   public void testCountBoundary() throws IOException {
     Directory dir = newDirectory();
     IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
-    SortField sortField = new SortedNumericSortField("field", SortField.Type.LONG);
     boolean useLower = random().nextBoolean();
     long lowerValue = 1;
     long upperValue = 100;
-    sortField.setMissingValue(useLower ? lowerValue : upperValue);
+    SortField sortField =
+        new SortedNumericSortField(
+            "field",
+            SortField.Type.LONG,
+            false,
+            SortedNumericSelector.Type.MIN,
+            useLower ? lowerValue : upperValue);
     Sort indexSort = new Sort(sortField);
     iwc.setIndexSort(indexSort);
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLongValuesSource.java b/lucene/core/src/test/org/apache/lucene/search/TestLongValuesSource.java
index 5732e61e3f9..53b2fd0b42d 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestLongValuesSource.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestLongValuesSource.java
@@ -90,16 +90,14 @@ public class TestLongValuesSource extends LuceneTestCase {
     LongValuesSource onefield = LongValuesSource.fromLongField("onefield");
 
     // sort decreasing, missing last
-    SortField oneFieldSort = onefield.getSortField(true);
-    oneFieldSort.setMissingValue(Long.MIN_VALUE);
+    SortField oneFieldSort = onefield.getSortField(true, Long.MIN_VALUE);
 
     TopDocs results = searcher.search(new MatchAllDocsQuery(), 1, new Sort(oneFieldSort));
     FieldDoc first = (FieldDoc) results.scoreDocs[0];
     assertEquals(LEAST_LONG_VALUE, first.fields[0]);
 
     // sort increasing, missing last
-    oneFieldSort = onefield.getSortField(false);
-    oneFieldSort.setMissingValue(Long.MAX_VALUE);
+    oneFieldSort = onefield.getSortField(false, Long.MAX_VALUE);
 
     results = searcher.search(new MatchAllDocsQuery(), 1, new Sort(oneFieldSort));
     first = (FieldDoc) results.scoreDocs[0];
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java b/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java
index ecc5727625c..56cac4c30cd 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSearchAfter.java
@@ -75,12 +75,11 @@ public class TestSearchAfter extends LuceneTestCase {
     for (String field : new String[] {"bytes", "sortedbytesdocvalues"}) {
       for (int rev = 0; rev < 2; rev++) {
         boolean reversed = rev == 0;
-        SortField sf = new SortField(field, SortField.Type.STRING, reversed);
-        sf.setMissingValue(SortField.STRING_FIRST);
+        SortField sf =
+            new SortField(field, SortField.Type.STRING, reversed, SortField.STRING_FIRST);
         allSortFields.add(sf);
 
-        sf = new SortField(field, SortField.Type.STRING, reversed);
-        sf.setMissingValue(SortField.STRING_LAST);
+        sf = new SortField(field, SortField.Type.STRING, reversed, SortField.STRING_LAST);
         allSortFields.add(sf);
       }
     }
@@ -89,12 +88,11 @@ public class TestSearchAfter extends LuceneTestCase {
     for (String field : new String[] {"sortedbytesdocvaluesval", "straightbytesdocvalues"}) {
       for (int rev = 0; rev < 2; rev++) {
         boolean reversed = rev == 0;
-        SortField sf = new SortField(field, SortField.Type.STRING_VAL, reversed);
-        sf.setMissingValue(SortField.STRING_FIRST);
+        SortField sf =
+            new SortField(field, SortField.Type.STRING_VAL, reversed, SortField.STRING_FIRST);
         allSortFields.add(sf);
 
-        sf = new SortField(field, SortField.Type.STRING_VAL, reversed);
-        sf.setMissingValue(SortField.STRING_LAST);
+        sf = new SortField(field, SortField.Type.STRING_VAL, reversed, SortField.STRING_LAST);
         allSortFields.add(sf);
       }
     }
@@ -103,20 +101,22 @@ public class TestSearchAfter extends LuceneTestCase {
     for (int i = 0; i < limit; i++) {
       SortField sf = allSortFields.get(i);
       if (sf.getType() == SortField.Type.INT) {
-        SortField sf2 = new SortField(sf.getField(), SortField.Type.INT, sf.getReverse());
-        sf2.setMissingValue(random().nextInt());
+        SortField sf2 =
+            new SortField(sf.getField(), SortField.Type.INT, sf.getReverse(), random().nextInt());
         allSortFields.add(sf2);
       } else if (sf.getType() == SortField.Type.LONG) {
-        SortField sf2 = new SortField(sf.getField(), SortField.Type.LONG, sf.getReverse());
-        sf2.setMissingValue(random().nextLong());
+        SortField sf2 =
+            new SortField(sf.getField(), SortField.Type.LONG, sf.getReverse(), random().nextLong());
         allSortFields.add(sf2);
       } else if (sf.getType() == SortField.Type.FLOAT) {
-        SortField sf2 = new SortField(sf.getField(), SortField.Type.FLOAT, sf.getReverse());
-        sf2.setMissingValue(random().nextFloat());
+        SortField sf2 =
+            new SortField(
+                sf.getField(), SortField.Type.FLOAT, sf.getReverse(), random().nextFloat());
         allSortFields.add(sf2);
       } else if (sf.getType() == SortField.Type.DOUBLE) {
-        SortField sf2 = new SortField(sf.getField(), SortField.Type.DOUBLE, sf.getReverse());
-        sf2.setMissingValue(random().nextDouble());
+        SortField sf2 =
+            new SortField(
+                sf.getField(), SortField.Type.DOUBLE, sf.getReverse(), random().nextDouble());
         allSortFields.add(sf2);
       }
     }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSort.java b/lucene/core/src/test/org/apache/lucene/search/TestSort.java
index db2c7bbe884..4a60095560c 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSort.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSort.java
@@ -16,6 +16,8 @@
  */
 package org.apache.lucene.search;
 
+import static org.hamcrest.Matchers.equalTo;
+
 import java.io.IOException;
 import org.apache.lucene.document.BinaryDocValuesField;
 import org.apache.lucene.document.Document;
@@ -79,8 +81,7 @@ public class TestSort extends LuceneTestCase {
     sortField2 = new SortField("foo", SortField.Type.LONG);
     assertDifferent(new Sort(sortField1), new Sort(sortField2));
 
-    sortField2 = new SortField("foo", SortField.Type.STRING);
-    sortField2.setMissingValue(SortField.STRING_FIRST);
+    sortField2 = new SortField("foo", SortField.Type.STRING, false, SortField.STRING_FIRST);
     assertDifferent(new Sort(sortField1), new Sort(sortField2));
 
     sortField2 = new SortField("foo", SortField.Type.STRING, false);
@@ -90,6 +91,18 @@ public class TestSort extends LuceneTestCase {
     assertDifferent(new Sort(sortField1), new Sort(sortField2));
   }
 
+  public void testStringMissingValues() {
+    Exception e =
+        expectThrows(
+            IllegalArgumentException.class,
+            () -> {
+              new Sort(new SortField("foo", SortField.Type.STRING, false, "invalid value"));
+            });
+    assertThat(
+        e.getMessage(),
+        equalTo("For Type.STRING, missing value must be either STRING_FIRST or STRING_LAST"));
+  }
+
   /** Tests sorting on type string */
   public void testString() throws IOException {
     Directory dir = newDirectory();
@@ -319,8 +332,7 @@ public class TestSort extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortField("value", SortField.Type.INT);
-    sortField.setMissingValue(Integer.MAX_VALUE);
+    SortField sortField = new SortField("value", SortField.Type.INT, false, Integer.MAX_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -451,8 +463,7 @@ public class TestSort extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortField("value", SortField.Type.LONG);
-    sortField.setMissingValue(Long.MAX_VALUE);
+    SortField sortField = new SortField("value", SortField.Type.LONG, false, Long.MAX_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -583,8 +594,7 @@ public class TestSort extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortField("value", SortField.Type.FLOAT);
-    sortField.setMissingValue(Float.MAX_VALUE);
+    SortField sortField = new SortField("value", SortField.Type.FLOAT, false, Float.MAX_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -770,8 +780,7 @@ public class TestSort extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortField("value", SortField.Type.DOUBLE);
-    sortField.setMissingValue(Double.MAX_VALUE);
+    SortField sortField = new SortField("value", SortField.Type.DOUBLE, false, Double.MAX_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSortOptimization.java b/lucene/core/src/test/org/apache/lucene/search/TestSortOptimization.java
index 3bc0cc1d024..becb84e0a5f 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSortOptimization.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSortOptimization.java
@@ -221,20 +221,17 @@ public class TestSortOptimization extends LuceneTestCase {
     final int numHits = 3;
 
     { // test that optimization is run when missing value setting of SortField is competitive with
-      // Puring.GREATER_THAN_OR_EQUAL_TO
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG);
-      sortField.setMissingValue(0L); // set a competitive missing value
+      // Pruning.GREATER_THAN_OR_EQUAL_TO
+      final SortField sortField = new SortField("my_field", SortField.Type.LONG, false, 0L);
       final Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, null);
       assertEquals(topDocs.scoreDocs.length, numHits);
       assertNonCompetitiveHitsAreSkipped(topDocs.totalHits.value(), numDocs);
     }
     { // test that optimization is not run when missing value setting of SortField is competitive
-      // with Puring.SKIP
-      final SortField sortField1 = new SortField("my_field1", SortField.Type.LONG);
-      final SortField sortField2 = new SortField("my_field2", SortField.Type.LONG);
-      sortField1.setMissingValue(0L); // set a competitive missing value
-      sortField2.setMissingValue(0L); // set a competitive missing value
+      // with Pruning.SKIP
+      final SortField sortField1 = new SortField("my_field1", SortField.Type.LONG, false, 0L);
+      final SortField sortField2 = new SortField("my_field2", SortField.Type.LONG, false, 0L);
       final Sort sort = new Sort(sortField1, sortField2);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, null);
       assertEquals(topDocs.scoreDocs.length, numHits);
@@ -243,8 +240,7 @@ public class TestSortOptimization extends LuceneTestCase {
           numDocs); // assert that all documents were collected => optimization was not run
     }
     { // test that optimization is run when missing value setting of SortField is NOT competitive
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG);
-      sortField.setMissingValue(100L); // set a NON competitive missing value
+      final SortField sortField = new SortField("my_field", SortField.Type.LONG, false, 100L);
       final Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, null);
       assertEquals(topDocs.scoreDocs.length, numHits);
@@ -256,8 +252,8 @@ public class TestSortOptimization extends LuceneTestCase {
       final long afterValue = Long.MAX_VALUE;
       final int afterDocID = 10 + random().nextInt(1000);
       FieldDoc after = new FieldDoc(afterDocID, Float.NaN, new Long[] {afterValue});
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG);
-      sortField.setMissingValue(Long.MAX_VALUE); // set a competitive missing value
+      final SortField sortField =
+          new SortField("my_field", SortField.Type.LONG, false, Long.MAX_VALUE);
       final Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, after);
       assertEquals(topDocs.scoreDocs.length, numHits);
@@ -269,8 +265,8 @@ public class TestSortOptimization extends LuceneTestCase {
       final long afterValue = Long.MAX_VALUE;
       final int afterDocID = 10 + random().nextInt(1000);
       FieldDoc after = new FieldDoc(afterDocID, Float.NaN, new Long[] {afterValue});
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG, true);
-      sortField.setMissingValue(Long.MAX_VALUE); // set a competitive missing value
+      final SortField sortField =
+          new SortField("my_field", SortField.Type.LONG, true, Long.MAX_VALUE);
       final Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, after);
       assertEquals(topDocs.scoreDocs.length, numHits);
@@ -282,8 +278,7 @@ public class TestSortOptimization extends LuceneTestCase {
       // with after on asc order
       long afterValue = 3L;
       FieldDoc after = new FieldDoc(3, Float.NaN, new Long[] {afterValue});
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG);
-      sortField.setMissingValue(2L);
+      final SortField sortField = new SortField("my_field", SortField.Type.LONG, false, 2L);
       final Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSearchHits(reader, sort, numHits, after);
       assertEquals(topDocs.scoreDocs.length, numHits);
@@ -336,16 +331,14 @@ public class TestSortOptimization extends LuceneTestCase {
     TopDocs topDocs2;
 
     { // Test that optimization is run with NumericDocValues when missing value is NOT competitive
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG, true);
-      sortField.setMissingValue(0L); // missing value is not competitive
+      final SortField sortField = new SortField("my_field", SortField.Type.LONG, true, 0L);
       final Sort sort = new Sort(sortField);
       topDocs1 = assertSearchHits(reader, sort, numHits, null);
       assertNonCompetitiveHitsAreSkipped(topDocs1.totalHits.value(), numDocs);
     }
     { // Test that sort on sorted numeric field without sort optimization and with sort optimization
       // produce the same results
-      final SortField sortField = new SortField("my_field", SortField.Type.LONG, true);
-      sortField.setMissingValue(0L); // missing value is not competitive
+      final SortField sortField = new SortField("my_field", SortField.Type.LONG, true, 0L);
       final Sort sort = new Sort(sortField);
       sortField.setOptimizeSortWithPoints(false);
       topDocs2 = assertSearchHits(reader, sort, numHits, null);
@@ -364,10 +357,9 @@ public class TestSortOptimization extends LuceneTestCase {
     }
 
     { // Test that we can't do optimization via NumericDocValues when there are multiple comparators
-      final SortField sortField1 = new SortField("my_field", SortField.Type.LONG, true);
-      final SortField sortField2 = new SortField("other", SortField.Type.LONG, true);
-      sortField1.setMissingValue(0L); // missing value is not competitive
-      sortField2.setMissingValue(0L); // missing value is not competitive
+      // missing values are not competitive
+      final SortField sortField1 = new SortField("my_field", SortField.Type.LONG, true, 0L);
+      final SortField sortField2 = new SortField("other", SortField.Type.LONG, true, 0L);
       final Sort multiSorts = new Sort(new SortField[] {sortField1, sortField2});
       TopDocs topDocs = assertSearchHits(reader, multiSorts, numHits, null);
       // can't optimization with NumericDocValues when there are multiple comparators
@@ -1059,16 +1051,17 @@ public class TestSortOptimization extends LuceneTestCase {
 
     { // simple ascending sort
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSort(reader, sort, numHits, null);
       assertNonCompetitiveHitsAreSkipped(topDocs.totalHits.value(), numDocs);
     }
 
     { // simple descending sort
-      SortField sortField = KeywordField.newSortField("my_field", true, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+      SortField sortField =
+          KeywordField.newSortField(
+              "my_field", true, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
       Sort sort = new Sort(sortField);
       TopDocs topDocs = assertSort(reader, sort, numHits, null);
       assertNonCompetitiveHitsAreSkipped(topDocs.totalHits.value(), numDocs);
@@ -1076,23 +1069,24 @@ public class TestSortOptimization extends LuceneTestCase {
 
     { // ascending sort that returns missing values first
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
       Sort sort = new Sort(sortField);
       assertSort(reader, sort, numHits, null);
     }
 
     { // descending sort that returns missing values last
-      SortField sortField = KeywordField.newSortField("my_field", true, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+      SortField sortField =
+          KeywordField.newSortField(
+              "my_field", true, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(sortField);
       assertSort(reader, sort, numHits, null);
     }
 
     { // paging ascending sort with after
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(sortField);
       BytesRef afterValue = new BytesRef(random().nextBoolean() ? "23" : "230000000");
       FieldDoc after = new FieldDoc(2, Float.NaN, new Object[] {afterValue});
@@ -1101,8 +1095,9 @@ public class TestSortOptimization extends LuceneTestCase {
     }
 
     { // paging descending sort with after
-      SortField sortField = KeywordField.newSortField("my_field", true, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+      SortField sortField =
+          KeywordField.newSortField(
+              "my_field", true, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
       Sort sort = new Sort(sortField);
       BytesRef afterValue = new BytesRef(random().nextBoolean() ? "17" : "170000000");
       FieldDoc after = new FieldDoc(2, Float.NaN, new Object[] {afterValue});
@@ -1112,8 +1107,8 @@ public class TestSortOptimization extends LuceneTestCase {
 
     { // paging ascending sort with after that returns missing values first
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_FIRST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
       Sort sort = new Sort(sortField);
       BytesRef afterValue = new BytesRef(random().nextBoolean() ? "23" : "230000000");
       FieldDoc after = new FieldDoc(2, Float.NaN, new Object[] {afterValue});
@@ -1122,8 +1117,9 @@ public class TestSortOptimization extends LuceneTestCase {
     }
 
     { // paging descending sort with after that returns missing values first
-      SortField sortField = KeywordField.newSortField("my_field", true, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+      SortField sortField =
+          KeywordField.newSortField(
+              "my_field", true, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(sortField);
       BytesRef afterValue = new BytesRef(random().nextBoolean() ? "17" : "170000000");
       FieldDoc after = new FieldDoc(2, Float.NaN, new Object[] {afterValue});
@@ -1133,8 +1129,8 @@ public class TestSortOptimization extends LuceneTestCase {
 
     { // test that if there is the secondary sort on _score, hits are still skipped
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(sortField, FIELD_SCORE);
       TopDocs topDocs = assertSort(reader, sort, numHits, null);
       assertNonCompetitiveHitsAreSkipped(topDocs.totalHits.value(), numDocs);
@@ -1142,8 +1138,8 @@ public class TestSortOptimization extends LuceneTestCase {
 
     { // test that if string field is a secondary sort, no optimization is run
       SortField sortField =
-          KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-      sortField.setMissingValue(SortField.STRING_LAST);
+          KeywordField.newSortField(
+              "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
       Sort sort = new Sort(FIELD_SCORE, sortField);
       TopDocs topDocs = assertSort(reader, sort, numHits, null);
       assertEquals(
@@ -1153,8 +1149,9 @@ public class TestSortOptimization extends LuceneTestCase {
   }
 
   public void doTestStringSortOptimizationDisabled(DirectoryReader reader) throws IOException {
-    SortField sortField = KeywordField.newSortField("my_field", false, SortedSetSelector.Type.MIN);
-    sortField.setMissingValue(SortField.STRING_LAST);
+    SortField sortField =
+        KeywordField.newSortField(
+            "my_field", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
     sortField.setOptimizeSortWithIndexedData(false);
 
     Sort sort = new Sort(sortField);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSortRandom.java b/lucene/core/src/test/org/apache/lucene/search/TestSortRandom.java
index 2ff71e266f2..46a935c5513 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSortRandom.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSortRandom.java
@@ -130,12 +130,8 @@ public class TestSortRandom extends LuceneTestCase {
       final TopFieldDocs hits;
       final SortField sf;
       final boolean sortMissingLast;
-      sf = new SortField("stringdv", type, reverse);
       sortMissingLast = random().nextBoolean();
-
-      if (sortMissingLast) {
-        sf.setMissingValue(SortField.STRING_LAST);
-      }
+      sf = new SortField("stringdv", type, reverse, sortMissingLast ? SortField.STRING_LAST : null);
 
       final Sort sort;
       if (random.nextBoolean()) {
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSortedNumericSortField.java b/lucene/core/src/test/org/apache/lucene/search/TestSortedNumericSortField.java
index e7f765a16f8..b6a3c91a5e1 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSortedNumericSortField.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSortedNumericSortField.java
@@ -153,8 +153,9 @@ public class TestSortedNumericSortField extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortedNumericSortField("value", SortField.Type.INT);
-    sortField.setMissingValue(Integer.MIN_VALUE);
+    SortField sortField =
+        new SortedNumericSortField(
+            "value", SortField.Type.INT, false, SortedNumericSelector.Type.MIN, Integer.MIN_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -188,8 +189,9 @@ public class TestSortedNumericSortField extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortedNumericSortField("value", SortField.Type.INT);
-    sortField.setMissingValue(Integer.MAX_VALUE);
+    SortField sortField =
+        new SortedNumericSortField(
+            "value", SortField.Type.INT, false, SortedNumericSelector.Type.MIN, Integer.MAX_VALUE);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSelector.java b/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSelector.java
index 2e41b26f70a..48b625ea4a2 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSelector.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSelector.java
@@ -113,8 +113,8 @@ public class TestSortedSetSelector extends LuceneTestCase {
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
 
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MAX);
-    sortField.setMissingValue(SortField.STRING_FIRST);
+    SortField sortField =
+        new SortedSetSortField("value", false, SortedSetSelector.Type.MAX, SortField.STRING_FIRST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -150,8 +150,8 @@ public class TestSortedSetSelector extends LuceneTestCase {
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
 
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MAX);
-    sortField.setMissingValue(SortField.STRING_LAST);
+    SortField sortField =
+        new SortedSetSortField("value", false, SortedSetSelector.Type.MAX, SortField.STRING_LAST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -278,8 +278,9 @@ public class TestSortedSetSelector extends LuceneTestCase {
 
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MIDDLE_MIN);
-    sortField.setMissingValue(SortField.STRING_FIRST);
+    SortField sortField =
+        new SortedSetSortField(
+            "value", false, SortedSetSelector.Type.MIDDLE_MIN, SortField.STRING_FIRST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -316,8 +317,9 @@ public class TestSortedSetSelector extends LuceneTestCase {
 
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MIDDLE_MIN);
-    sortField.setMissingValue(SortField.STRING_LAST);
+    SortField sortField =
+        new SortedSetSortField(
+            "value", false, SortedSetSelector.Type.MIDDLE_MIN, SortField.STRING_LAST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -444,8 +446,9 @@ public class TestSortedSetSelector extends LuceneTestCase {
 
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MIDDLE_MAX);
-    sortField.setMissingValue(SortField.STRING_FIRST);
+    SortField sortField =
+        new SortedSetSortField(
+            "value", false, SortedSetSelector.Type.MIDDLE_MAX, SortField.STRING_FIRST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -482,8 +485,9 @@ public class TestSortedSetSelector extends LuceneTestCase {
 
     // slow wrapper does not support random access ordinals (there is no need for that!)
     IndexSearcher searcher = newSearcher(ir, false);
-    SortField sortField = new SortedSetSortField("value", false, SortedSetSelector.Type.MIDDLE_MAX);
-    sortField.setMissingValue(SortField.STRING_LAST);
+    SortField sortField =
+        new SortedSetSortField(
+            "value", false, SortedSetSelector.Type.MIDDLE_MAX, SortField.STRING_LAST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSortField.java b/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSortField.java
index 939580125b2..a90a7c6e1da 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSortField.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestSortedSetSortField.java
@@ -136,8 +136,8 @@ public class TestSortedSetSortField extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortedSetSortField("value", false);
-    sortField.setMissingValue(SortField.STRING_FIRST);
+    SortField sortField =
+        new SortedSetSortField("value", false, SortedSetSelector.Type.MIN, SortField.STRING_FIRST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
@@ -171,8 +171,8 @@ public class TestSortedSetSortField extends LuceneTestCase {
     writer.close();
 
     IndexSearcher searcher = newSearcher(ir);
-    SortField sortField = new SortedSetSortField("value", false);
-    sortField.setMissingValue(SortField.STRING_LAST);
+    SortField sortField =
+        new SortedSetSortField("value", false, SortedSetSelector.Type.MIN, SortField.STRING_LAST);
     Sort sort = new Sort(sortField);
 
     TopDocs td = searcher.search(new MatchAllDocsQuery(), 10, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestXYPointDistanceSort.java b/lucene/core/src/test/org/apache/lucene/search/TestXYPointDistanceSort.java
index d0656d22edd..27d1aba7528 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestXYPointDistanceSort.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestXYPointDistanceSort.java
@@ -237,7 +237,6 @@ public class TestXYPointDistanceSort extends LuceneTestCase {
       int topN = TestUtil.nextInt(random(), 1, reader.maxDoc());
       // sort by distance, then ID
       SortField distanceSort = XYDocValuesField.newDistanceSort("field", x, y);
-      distanceSort.setMissingValue(missingValue);
       Sort sort = new Sort(distanceSort, new SortField("id", SortField.Type.INT));
 
       TopDocs topDocs = searcher.search(new MatchAllDocsQuery(), topN, sort);
diff --git a/lucene/core/src/test/org/apache/lucene/search/comparators/TestNumericComparator.java b/lucene/core/src/test/org/apache/lucene/search/comparators/TestNumericComparator.java
index 77de19b792a..28c548be08a 100644
--- a/lucene/core/src/test/org/apache/lucene/search/comparators/TestNumericComparator.java
+++ b/lucene/core/src/test/org/apache/lucene/search/comparators/TestNumericComparator.java
@@ -139,8 +139,8 @@ public class TestNumericComparator extends LuceneTestCase {
           var indexSearcher = newSearcher(reader);
           indexSearcher.setQueryCache(null);
           {
-            var sortField = new SortField("long_field_1", SortField.Type.LONG, false);
-            sortField.setMissingValue(Long.MIN_VALUE);
+            var sortField =
+                new SortField("long_field_1", SortField.Type.LONG, false, Long.MIN_VALUE);
             var topDocs = indexSearcher.search(new MatchAllDocsQuery(), 3, new Sort(sortField));
             assertEquals(numDocs, topDocs.scoreDocs[0].doc);
             assertEquals(Long.MIN_VALUE, ((FieldDoc) topDocs.scoreDocs[0]).fields[0]);
@@ -150,8 +150,8 @@ public class TestNumericComparator extends LuceneTestCase {
             assertEquals(1L, ((FieldDoc) topDocs.scoreDocs[2]).fields[0]);
           }
           {
-            var sortField = new SortField("long_field_2", SortField.Type.LONG, false);
-            sortField.setMissingValue(Long.MIN_VALUE);
+            var sortField =
+                new SortField("long_field_2", SortField.Type.LONG, false, Long.MIN_VALUE);
             var topDocs = indexSearcher.search(new MatchAllDocsQuery(), 3, new Sort(sortField));
             assertEquals(numDocs, topDocs.scoreDocs[0].doc);
             assertEquals(Long.MIN_VALUE, ((FieldDoc) topDocs.scoreDocs[0]).fields[0]);
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
index f0b8a10adb5..d8fae27e982 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedDoubleFieldSource.java
@@ -37,10 +37,16 @@ import org.apache.lucene.search.SortedNumericSortField;
 public class MultiValuedDoubleFieldSource extends DoubleFieldSource {
 
   protected final SortedNumericSelector.Type selector;
+  private final Double missingValue;
 
   public MultiValuedDoubleFieldSource(String field, Type selector) {
+    this(field, selector, null);
+  }
+
+  public MultiValuedDoubleFieldSource(String field, Type selector, Double missingValue) {
     super(field);
     this.selector = selector;
+    this.missingValue = missingValue;
     Objects.requireNonNull(field, "Field is required to create a MultiValuedDoubleFieldSource");
     Objects.requireNonNull(
         selector, "SortedNumericSelector is required to create a MultiValuedDoubleFieldSource");
@@ -48,7 +54,8 @@ public class MultiValuedDoubleFieldSource extends DoubleFieldSource {
 
   @Override
   public SortField getSortField(boolean reverse) {
-    return new SortedNumericSortField(field, SortField.Type.DOUBLE, reverse, selector);
+    return new SortedNumericSortField(
+        field, SortField.Type.DOUBLE, reverse, selector, missingValue);
   }
 
   @Override
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
index b06d0ebe640..34d76ab083a 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedFloatFieldSource.java
@@ -37,10 +37,16 @@ import org.apache.lucene.search.SortedNumericSortField;
 public class MultiValuedFloatFieldSource extends FloatFieldSource {
 
   protected final SortedNumericSelector.Type selector;
+  private final Float missingValue;
 
-  public MultiValuedFloatFieldSource(String field, Type selector) {
+  public MultiValuedFloatFieldSource(String field, SortedNumericSelector.Type selector) {
+    this(field, selector, null);
+  }
+
+  public MultiValuedFloatFieldSource(String field, Type selector, Float missingValue) {
     super(field);
     this.selector = selector;
+    this.missingValue = missingValue;
     Objects.requireNonNull(field, "Field is required to create a MultiValuedFloatFieldSource");
     Objects.requireNonNull(
         selector, "SortedNumericSelector is required to create a MultiValuedFloatFieldSource");
@@ -48,7 +54,7 @@ public class MultiValuedFloatFieldSource extends FloatFieldSource {
 
   @Override
   public SortField getSortField(boolean reverse) {
-    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.FLOAT, reverse, selector, missingValue);
   }
 
   @Override
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
index c60f1880cbf..00fe2690a7b 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedIntFieldSource.java
@@ -37,10 +37,16 @@ import org.apache.lucene.search.SortedNumericSortField;
 public class MultiValuedIntFieldSource extends IntFieldSource {
 
   protected final SortedNumericSelector.Type selector;
+  private final Integer missingValue;
 
-  public MultiValuedIntFieldSource(String field, Type selector) {
+  public MultiValuedIntFieldSource(String field, SortedNumericSelector.Type selector) {
+    this(field, selector, null);
+  }
+
+  public MultiValuedIntFieldSource(String field, Type selector, Integer missingValue) {
     super(field);
     this.selector = selector;
+    this.missingValue = missingValue;
     Objects.requireNonNull(field, "Field is required to create a MultiValuedIntFieldSource");
     Objects.requireNonNull(
         selector, "SortedNumericSelector is required to create a MultiValuedIntFieldSource");
@@ -48,7 +54,7 @@ public class MultiValuedIntFieldSource extends IntFieldSource {
 
   @Override
   public SortField getSortField(boolean reverse) {
-    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.INT, reverse, selector, missingValue);
   }
 
   @Override
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
index 20a5fe9c18a..3adb6651845 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiValuedLongFieldSource.java
@@ -37,10 +37,16 @@ import org.apache.lucene.search.SortedNumericSortField;
 public class MultiValuedLongFieldSource extends LongFieldSource {
 
   protected final SortedNumericSelector.Type selector;
+  private final Long missingValue;
 
-  public MultiValuedLongFieldSource(String field, Type selector) {
+  public MultiValuedLongFieldSource(String field, SortedNumericSelector.Type selector) {
+    this(field, selector, null);
+  }
+
+  public MultiValuedLongFieldSource(String field, Type selector, Long missingValue) {
     super(field);
     this.selector = selector;
+    this.missingValue = missingValue;
     Objects.requireNonNull(field, "Field is required to create a MultiValuedLongFieldSource");
     Objects.requireNonNull(
         selector, "SortedNumericSelector is required to create a MultiValuedLongFieldSource");
@@ -48,7 +54,7 @@ public class MultiValuedLongFieldSource extends LongFieldSource {
 
   @Override
   public SortField getSortField(boolean reverse) {
-    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector);
+    return new SortedNumericSortField(field, SortField.Type.LONG, reverse, selector, missingValue);
   }
 
   @Override
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
index b0cac135603..d45dd5a681c 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointOutsideSortField.java
@@ -29,7 +29,7 @@ final class Geo3DPointOutsideSortField extends SortField {
 
   Geo3DPointOutsideSortField(
       final String field, final PlanetModel planetModel, final GeoOutsideDistance distanceShape) {
-    super(field, SortField.Type.CUSTOM);
+    super(field, SortField.Type.CUSTOM, false, Double.POSITIVE_INFINITY);
     if (field == null) {
       throw new IllegalArgumentException("field must not be null");
     }
@@ -38,7 +38,6 @@ final class Geo3DPointOutsideSortField extends SortField {
     }
     this.planetModel = planetModel;
     this.distanceShape = distanceShape;
-    setMissingValue(Double.POSITIVE_INFINITY);
   }
 
   @Override
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
index 49caa7250a6..1873cc94e5a 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPointSortField.java
@@ -29,7 +29,7 @@ final class Geo3DPointSortField extends SortField {
 
   Geo3DPointSortField(
       final String field, final PlanetModel planetModel, final GeoDistanceShape distanceShape) {
-    super(field, SortField.Type.CUSTOM);
+    super(field, SortField.Type.CUSTOM, false, Double.POSITIVE_INFINITY);
     if (field == null) {
       throw new IllegalArgumentException("field must not be null");
     }
@@ -38,7 +38,6 @@ final class Geo3DPointSortField extends SortField {
     }
     this.distanceShape = distanceShape;
     this.planetModel = planetModel;
-    setMissingValue(Double.POSITIVE_INFINITY);
   }
 
   @Override
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseSegmentInfoFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseSegmentInfoFormatTestCase.java
index 6a827f73a2d..f1d4452b78a 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseSegmentInfoFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/index/BaseSegmentInfoFormatTestCase.java
@@ -30,7 +30,9 @@ import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.SegmentInfo;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSelector;
 import org.apache.lucene.search.SortedNumericSortField;
+import org.apache.lucene.search.SortedSetSelector;
 import org.apache.lucene.search.SortedSetSortField;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IOContext;
@@ -293,87 +295,76 @@ public abstract class BaseSegmentInfoFormatTestCase extends BaseIndexFileFormatT
 
   private SortField randomIndexSortField() {
     boolean reversed = random().nextBoolean();
-    SortField sortField;
-    switch (random().nextInt(10)) {
-      case 0:
-        sortField =
-            new SortField(TestUtil.randomSimpleString(random()), SortField.Type.INT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextInt());
-        }
-        break;
-      case 1:
-        sortField =
-            new SortedNumericSortField(
-                TestUtil.randomSimpleString(random()), SortField.Type.INT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextInt());
-        }
-        break;
-
-      case 2:
-        sortField =
-            new SortField(TestUtil.randomSimpleString(random()), SortField.Type.LONG, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextLong());
-        }
-        break;
-      case 3:
-        sortField =
-            new SortedNumericSortField(
-                TestUtil.randomSimpleString(random()), SortField.Type.LONG, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextLong());
-        }
-        break;
-      case 4:
-        sortField =
-            new SortField(TestUtil.randomSimpleString(random()), SortField.Type.FLOAT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextFloat());
-        }
-        break;
-      case 5:
-        sortField =
-            new SortedNumericSortField(
-                TestUtil.randomSimpleString(random()), SortField.Type.FLOAT, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextFloat());
-        }
-        break;
-      case 6:
-        sortField =
-            new SortField(TestUtil.randomSimpleString(random()), SortField.Type.DOUBLE, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextDouble());
-        }
-        break;
-      case 7:
-        sortField =
-            new SortedNumericSortField(
-                TestUtil.randomSimpleString(random()), SortField.Type.DOUBLE, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(random().nextDouble());
-        }
-        break;
-      case 8:
-        sortField =
-            new SortField(TestUtil.randomSimpleString(random()), SortField.Type.STRING, reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(SortField.STRING_LAST);
-        }
-        break;
-      case 9:
-        sortField = new SortedSetSortField(TestUtil.randomSimpleString(random()), reversed);
-        if (random().nextBoolean()) {
-          sortField.setMissingValue(SortField.STRING_LAST);
-        }
-        break;
-      default:
-        sortField = null;
+    return switch (random().nextInt(10)) {
+      case 0 ->
+          new SortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.INT,
+              reversed,
+              random().nextBoolean() ? random().nextInt() : null);
+      case 1 ->
+          new SortedNumericSortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.INT,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextInt() : null);
+      case 2 ->
+          new SortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.LONG,
+              reversed,
+              random().nextBoolean() ? random().nextLong() : null);
+      case 3 ->
+          new SortedNumericSortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.LONG,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextLong() : null);
+      case 4 ->
+          new SortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.FLOAT,
+              reversed,
+              random().nextBoolean() ? random().nextFloat() : null);
+      case 5 ->
+          new SortedNumericSortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.FLOAT,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextFloat() : null);
+      case 6 ->
+          new SortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.DOUBLE,
+              reversed,
+              random().nextBoolean() ? random().nextDouble() : null);
+      case 7 ->
+          new SortedNumericSortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.DOUBLE,
+              reversed,
+              SortedNumericSelector.Type.MIN,
+              random().nextBoolean() ? random().nextDouble() : null);
+      case 8 ->
+          new SortField(
+              TestUtil.randomSimpleString(random()),
+              SortField.Type.STRING,
+              reversed,
+              random().nextBoolean() ? SortField.STRING_LAST : null);
+      case 9 ->
+          new SortedSetSortField(
+              TestUtil.randomSimpleString(random()),
+              reversed,
+              SortedSetSelector.Type.MIN,
+              random().nextBoolean() ? SortField.STRING_LAST : null);
+      default -> {
         fail();
-    }
-    return sortField;
+        yield null;
+      }
+    };
   }
 
   /** Test sort */