Java Multi-Key Sorting for Dynamic Map Collections

When sorting complex data structures in Java, especially when dealing with dynamic or variable-length sorting criteria, leveraging custom comparators provides fine-grained control over ordering logic. This approach is particularly useful when replicating SQL-style ORDER BY behavior in memory.

Handling Fixed-Key Sorting

For a known set of sort keys, such as ordering books by author (ascending), price (descending), and publisher (ascending), a traditional Comparator can be implemented with cascading comparisons:

Collections.sort(bookList, new Comparator<Map<String, Object>>() {
    @Override
    public int compare(Map<String, Object> a, Map<String, Object> b) {
        // Primary: author (ascending)
        int authorComp = ((String) a.get("author")).compareTo((String) b.get("author"));
        if (authorComp != 0) return authorComp;

        // Secondary: price (descending)
        BigDecimal priceA = new BigDecimal(a.get("price").toString());
        BigDecimal priceB = new BigDecimal(b.get("price").toString());
        int priceComp = priceA.compareTo(priceB);
        if (priceComp != 0) return -priceComp; // Reverse for descending

        // Tertiary: publisher (ascending)
        return ((String) a.get("publisher")).compareTo((String) b.get("publisher"));
    }
});

This pattern ensures that each subsequent key is only evaluated when the previous one yields equality. The descending order for price is achieved by negating the comparison result.

Dynamic Multi-Key Sorting

When the number and order of sort fields are determined at runtime — for example, via user configuration or API parameters — a flexible solution is required. Here’s a scalable approach using configuration-driven sorting:

public void dynamicSort(List<Map<String, Object>> records, List<Map<String, Object>> sortConfig) {
    // Sort configuration by priority (sortPosition)
    sortConfig.sort((cfg1, cfg2) -> 
        Integer.compare(
            Integer.parseInt(cfg1.get("sortPosition").toString()),
            Integer.parseInt(cfg2.get("sortPosition").toString())
        )
    );

    // Extract sort criteria into arrays for efficiency
    String[] fieldNames = new String[sortConfig.size()];
    int[] directions = new int[sortConfig.size()];

    for (int i = 0; i < sortConfig.size(); i++) {
        Map<String, Object> config = sortConfig.get(i);
        fieldNames[i] = config.get("code").toString();
        directions[i] = "asc".equalsIgnoreCase(config.get("sortDirection").toString()) ? 1 : -1;
    }

    // Apply dynamic sorting
    records.sort((record1, record2) -> {
        for (int i = 0; i < fieldNames.length; i++) {
            Object val1 = record1.get(fieldNames[i]);
            Object val2 = record2.get(fieldNames[i]);

            int comparison = 0;
            if (val1 instanceof String && val2 instanceof String) {
                comparison = ((String) val1).compareTo((String) val2);
            } else {
                BigDecimal num1 = new BigDecimal(val1.toString());
                BigDecimal num2 = new BigDecimal(val2.toString());
                comparison = num1.compareTo(num2);
            }

            if (comparison != 0) {
                return comparison * directions[i];
            }
        }
        return 0; // Equal if all fields match
    });
}

In this implementation:

  • Sort rules are defined as a list of maps, each specifying the feild name, priority (position), and direction.
  • The configuration is pre-sorted by position to ensure correct evaluation order.
  • Each field is compared in sequence, and the first non-zero result determines the final order, adjusted by its direction (1 for ascending, -1 for descending).
  • Only basic types (String and numeric types convertible to BigDecimal) are supported, but the structure can be extended for dates, booleans, etc.

Considerations

  • This method is suitable for datasets under 10,000 records where latency is not critical.
  • Type handling is minimal; production systems should validate and cast types explicitly.
  • For high-throughput scenarios, consider pre-sorting at the database layer or using indexed collections.
  • String comparisons use Unicode ordering, which may differ from database collation rules (e.g., MySQL’s case-insensitive defaults).

Tags: java comparator Sorting DynamicSorting Map

Posted on Sat, 10 Oct 2026 16:31:24 +0000 by JWitness