2929import org .springframework .core .annotation .RepeatableContainers ;
3030import org .springframework .data .domain .Sort ;
3131import org .springframework .data .domain .Sort .Direction ;
32+ import org .springframework .data .domain .Sort .NullHandling ;
3233import org .springframework .data .domain .Sort .Order ;
3334import org .springframework .data .web .SortDefault .SortDefaults ;
3435import org .springframework .util .Assert ;
4142 * @author Mark Paluch
4243 * @author Vedran Pavic
4344 * @author Johannes Englmeier
45+ * @author Petar Heyken
4446 * @see SortHandlerMethodArgumentResolver
4547 * @see ReactiveSortHandlerMethodArgumentResolver
4648 * @since 2.2
@@ -165,7 +167,14 @@ private Sort appendOrCreateSortTo(MergedAnnotation<SortDefault> sortDefault, Sor
165167 List <Order > orders = new ArrayList <>(fields .length );
166168 for (String field : fields ) {
167169
168- Order order = new Order (sortDefault .getEnum ("direction" , Sort .Direction .class ), field );
170+ Order order = new Order (sortDefault .getEnum ("direction" , Direction .class ), field );
171+
172+ order = switch (sortDefault .getEnum ("nullHandling" , NullHandling .class )) {
173+ case NATIVE -> order .nullsNative ();
174+ case NULLS_FIRST -> order .nullsFirst ();
175+ case NULLS_LAST -> order .nullsLast ();
176+ };
177+
169178 orders .add (sortDefault .getBoolean ("caseSensitive" ) ? order : order .ignoreCase ());
170179 }
171180
@@ -214,6 +223,7 @@ Sort parseParameterIntoSort(List<String> source, String delimiter) {
214223 }
215224
216225 SortOrderParser .parse (part , delimiter ) //
226+ .parseNullHandling () //
217227 .parseIgnoreCase () //
218228 .parseDirection () //
219229 .forEachOrder (allOrders ::add );
@@ -360,22 +370,28 @@ List<String> dumpExpressionIfPresentInto(List<String> expressions) {
360370 static class SortOrderParser {
361371
362372 private static final String IGNORECASE = "ignorecase" ;
373+ private static final String NULLSNATIVE = "nullsnative" ;
374+ private static final String NULLSFIRST = "nullsfirst" ;
375+ private static final String NULLSLAST = "nullslast" ;
363376
364377 private final String [] elements ;
365378 private final int lastIndex ;
366379 private final Optional <Direction > direction ;
367380 private final Optional <Boolean > ignoreCase ;
381+ private final Optional <NullHandling > nullHandling ;
368382
369383 private SortOrderParser (String [] elements ) {
370- this (elements , elements .length , Optional .empty (), Optional .empty ());
384+ this (elements , elements .length , Optional .empty (), Optional .empty (), Optional . empty () );
371385 }
372386
373387 private SortOrderParser (String [] elements , int lastIndex , Optional <Direction > direction ,
374- Optional <Boolean > ignoreCase ) {
388+ Optional <Boolean > ignoreCase , Optional <NullHandling > nullHandling ) {
389+
375390 this .elements = elements ;
376391 this .lastIndex = Math .max (0 , lastIndex );
377392 this .direction = direction ;
378393 this .ignoreCase = ignoreCase ;
394+ this .nullHandling = nullHandling ;
379395 }
380396
381397 /**
@@ -394,16 +410,34 @@ public static SortOrderParser parse(String part, String delimiter) {
394410 return new SortOrderParser (elements );
395411 }
396412
413+ /**
414+ * Parse the {@link NullHandling} portion of the sort specification.
415+ *
416+ * @return a new parsing state object.
417+ */
418+ public SortOrderParser parseNullHandling () {
419+
420+ Optional <NullHandling > nullHandling = lastIndex > 0 ?
421+ fromOptionalNullHandlingString (elements [lastIndex - 1 ]) :
422+ Optional .empty ();
423+
424+ return new SortOrderParser (elements , lastIndex - (nullHandling .isPresent () ? 1 : 0 ), direction , ignoreCase ,
425+ nullHandling );
426+ }
427+
397428 /**
398429 * Parse the {@code ignoreCase} portion of the sort specification.
399430 *
400431 * @return a new parsing state object.
401432 */
402433 public SortOrderParser parseIgnoreCase () {
403434
404- Optional <Boolean > ignoreCase = lastIndex > 0 ? fromOptionalString (elements [lastIndex - 1 ]) : Optional .empty ();
435+ Optional <Boolean > ignoreCase = lastIndex > 0 ?
436+ fromOptionalIgnoreCaseString (elements [lastIndex - 1 ]) :
437+ Optional .empty ();
405438
406- return new SortOrderParser (elements , lastIndex - (ignoreCase .isPresent () ? 1 : 0 ), direction , ignoreCase );
439+ return new SortOrderParser (elements , lastIndex - (ignoreCase .isPresent () ? 1 : 0 ), direction , ignoreCase ,
440+ nullHandling );
407441 }
408442
409443 /**
@@ -416,7 +450,8 @@ public SortOrderParser parseDirection() {
416450 Optional <Direction > direction = lastIndex > 0 ? Direction .fromOptionalString (elements [lastIndex - 1 ])
417451 : Optional .empty ();
418452
419- return new SortOrderParser (elements , lastIndex - (direction .isPresent () ? 1 : 0 ), direction , ignoreCase );
453+ return new SortOrderParser (elements , lastIndex - (direction .isPresent () ? 1 : 0 ), direction , ignoreCase ,
454+ nullHandling );
420455 }
421456
422457 /**
@@ -431,7 +466,24 @@ public void forEachOrder(Consumer<? super Order> callback) {
431466 }
432467 }
433468
434- private Optional <Boolean > fromOptionalString (String value ) {
469+ private Optional <NullHandling > fromOptionalNullHandlingString (String value ) {
470+
471+ if (NULLSNATIVE .equalsIgnoreCase (value )) {
472+ return Optional .of (NullHandling .NATIVE );
473+ }
474+
475+ if (NULLSFIRST .equalsIgnoreCase (value )) {
476+ return Optional .of (NullHandling .NULLS_FIRST );
477+ }
478+
479+ if (NULLSLAST .equalsIgnoreCase (value )) {
480+ return Optional .of (NullHandling .NULLS_LAST );
481+ }
482+
483+ return Optional .empty ();
484+ }
485+
486+ private Optional <Boolean > fromOptionalIgnoreCaseString (String value ) {
435487 return IGNORECASE .equalsIgnoreCase (value ) ? Optional .of (true ) : Optional .empty ();
436488 }
437489
@@ -443,6 +495,14 @@ private Optional<Order> toOrder(String property) {
443495
444496 Order order = direction .map (it -> new Order (it , property )).orElseGet (() -> Order .by (property ));
445497
498+ if (nullHandling .isPresent ()) {
499+ order = switch (nullHandling .get ()) {
500+ case NATIVE -> order .nullsNative ();
501+ case NULLS_FIRST -> order .nullsFirst ();
502+ case NULLS_LAST -> order .nullsLast ();
503+ };
504+ }
505+
446506 if (ignoreCase .isPresent ()) {
447507 return Optional .of (order .ignoreCase ());
448508 }
0 commit comments