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.