Affects: spring-web:5.3.9 Original Problem from stackoverflow

Implementation of QUALITY_VALUE_COMPARATOR seems violate general contract of comparator.

The issue can be reproduced by below program. OS: Window 10 JDK: openjdk version "16" 2021-03-16

import org.springframework.http.MediaType;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class TestMediaTypeSort {
    public static void main(String[] args) {
        demonstrateViolation();
        reproducibleExample();
    }

    private static void reproducibleExample() {
        Map<String, String> mapSize1 = Map.of("a", "b");
        Map<String, String> mapSize2 = Map.of("a", "b", "c", "d");
        Map<String, String> mapSize3 = Map.of("a", "b", "c", "d", "e", "f");
        List<MediaType> mediaTypeList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mediaTypeList.add(new MediaType("c", "a", mapSize1));
            mediaTypeList.add(new MediaType("c", "a", mapSize2));
            mediaTypeList.add(new MediaType("c", "a", mapSize3));
            mediaTypeList.add(new MediaType("b", "a", mapSize1));
            mediaTypeList.add(new MediaType("b", "a", mapSize2));
            mediaTypeList.add(new MediaType("b", "a", mapSize3));
            mediaTypeList.add(new MediaType("b", "a", mapSize2));
        }
        MediaType.sortBySpecificityAndQuality(mediaTypeList);
    }

    private static void demonstrateViolation() {
        Map<String, String> mapSize1 = Map.of("a", "b");
        Map<String, String> mapSize2 = Map.of("a", "b", "c", "d");
        Map<String, String> mapSize3 = Map.of("a", "b", "c", "d", "e", "f");
        MediaType a = new MediaType("c", "a", mapSize1);
        MediaType b = new MediaType("b", "a", mapSize2);
        MediaType c = new MediaType("b", "a", mapSize3);
        System.out.println("Compare a with b:" + QUALITY_VALUE_COMPARATOR.compare(a, b));
        System.out.println("Compare a with c:" + QUALITY_VALUE_COMPARATOR.compare(a, c));
        System.out.println("Compare b with c:" + QUALITY_VALUE_COMPARATOR.compare(b, c));
    }

    // Copied from MediaType.java
    public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
        double quality1 = mediaType1.getQualityValue();
        double quality2 = mediaType2.getQualityValue();
        int qualityComparison = Double.compare(quality2, quality1);
        if (qualityComparison != 0) {
            return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
        } else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) {  // */* < audio/*
            return 1;
        } else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) {  // audio/* > */*
            return -1;
        } else if (!mediaType1.getType().equals(mediaType2.getType())) {  // audio/basic == text/html
            return 0;
        } else {  // mediaType1.getType().equals(mediaType2.getType())
            if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) {  // audio/* < audio/basic
                return 1;
            } else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) {  // audio/basic > audio/*
                return -1;
            } else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) {  // audio/basic == audio/wave
                return 0;
            } else {
                int paramsSize1 = mediaType1.getParameters().size();
                int paramsSize2 = mediaType2.getParameters().size();
                return Integer.compare(paramsSize2, paramsSize1);  // audio/basic;level=1 < audio/basic
            }
        }
    };
}


Comment From: poutsma

Unfortunately, the changes made to fix this issue had undesirable consequences (#27573). Therefore, we have reverted these change for the 5.3.x branch, and will revisiting Media type ordering in 6.0 (#27580).

Comment From: Ryokki

I met the same question in spring 5.3.13, what can I do?

Comment From: poutsma

I met the same question in spring 5.3.13, what can I do?

Upgrade to 6.0. As you can read above, we could not fix this without breaking backward compatibility, so a 5.3.x fix is not forthcoming.