Pair in Java: Map.entry, Records, Commons Lang and Vavr

Pair in Java with Map.entry(), AbstractMap.SimpleEntry, records, javafx.util.Pair, Commons Lang Pair and Vavr Tuple2, plus a guide to choose one.

Decision tree for choosing between a record, Map.entry, AbstractMap.SimpleEntry, Commons Lang Pair and Vavr Tuple2

A pair in Java groups two related values, such as a key and its value or a word and its count, into one object. The JDK has no general-purpose Pair class, so we use Map.entry(), a two-component record, or the Pair classes from Apache Commons Lang and Vavr.

We need a pair when a method returns two values, when a stream step must carry a value together with something computed from it, or when we keep a list of key-value items that should not become a Map, for example because keys repeat.

The following example creates the same pair, “apple” with 5, in the four most common ways.

Map.Entry<String, Integer> entry = Map.entry("apple", 5);         // apple=5
Pair<String, Integer> commons = Pair.of("apple", 5);              // (apple,5)
Tuple2<String, Integer> vavr = Tuple.of("apple", 5);              // (apple, 5)
FruitCount record = new FruitCount("apple", 5);                   // FruitCount[fruit=apple, count=5]
record FruitCount(String fruit, int count) {}

Notice that only the record says what the two values mean. We look at why Java has no Pair class, cover the JDK options, javafx.util.Pair, Apache Commons Lang and Vavr, and finish with a comparison table for picking one.

1. Why Java Has No Pair Class

A java.util.Pair class was proposed on the OpenJDK mailing list, and the reviewers argued against it because a class with real names, such as FruitCount with fruit and count, models the data better than getLeft() and getRight(). The JDK never added one. Since Java 16, records make such a class a one-line declaration, which removes most of the reasons to want a generic pair.

Decision tree for choosing between a record, Map.entry, AbstractMap.SimpleEntry, Commons Lang Pair and Vavr Tuple2
A record is the default; the other pair classes fit when an API expects them or the project already uses that library

A Map also stores key-value pairs, but it stores many of them, keeps keys unique and looks values up by key. A pair is a single, standalone object. We use a pair when two values must travel together through a method call, a list or a stream, and a lookup by key is not needed.

2. Pairs in the JDK

The JDK offers three ways to hold two values without any library. The first two implement Map.Entry, which many JDK APIs accept and return.

2.1. Map.entry() for an Immutable Pair

Since Java 9, Map.entry(k, v) returns an unmodifiable Map.Entry. It is the shortest built-in pair, and it fits well with Map.ofEntries(). It rejects null for both the key and the value.

Map.Entry<String, Integer> entry = Map.entry("apple", 5);         // apple=5
String fruit = entry.getKey();                                     // "apple"
Integer count = entry.getValue();                                  // 5
boolean same = entry.equals(Map.entry("apple", 5));                // true
Integer old = entry.setValue(6);                                   // UnsupportedOperationException
Map.Entry<String, Integer> empty = Map.entry("apple", null);       // NullPointerException

2.2. AbstractMap.SimpleEntry and SimpleImmutableEntry

When a pair must hold null, or its value must change, we use the two nested classes of AbstractMap. The SimpleEntry class allows setValue(), whereas SimpleImmutableEntry throws an exception. Neither one allows changing the key.

Map.Entry<String, Integer> mutable = new AbstractMap.SimpleEntry<>("apple", 5);
Integer previous = mutable.setValue(6);                            // 5
Integer current = mutable.getValue();                              // 6
Map.Entry<String, Integer> fixed = new AbstractMap.SimpleImmutableEntry<>("apple", null);  // apple=null
Integer rejected = fixed.setValue(7);                              // UnsupportedOperationException

All three Map.Entry classes use the same equals() and hashCode() contract, so Map.entry(“apple”, 5) equals new AbstractMap.SimpleEntry<>(“apple”, 5).

2.3. A Record as a Pair

A record with two components is the clearest pair, because the names explain the values and the compiler generates the constructor, accessors, equals(), hashCode() and toString().

Consider a reading app that shows the three most frequent words in a review. The stream counts words into a map, and each result row is a word with its count, which a record such as WordCount describes better than a Map.Entry.

record WordCount(String word, long count) {}
static List<WordCount> topWords(String text, int limit) {
    Map<String, Long> counts = Arrays.stream(text.toLowerCase().split("\\W+"))
        .collect(Collectors.groupingBy(w -> w, Collectors.counting()));
    return counts.entrySet().stream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed()
            .thenComparing(Map.Entry.comparingByKey()))
        .limit(limit)
        .map(e -> new WordCount(e.getKey(), e.getValue()))
        .toList();
}
List<WordCount> top = topWords("good plot, good cast, slow plot, good end", 2);  // [WordCount[word=good, count=3], WordCount[word=plot, count=2]]
String first = top.get(0).word();                                  // "good"

We can see that the Map.Entry objects stay inside the stream, where the JDK hands them to us, while the method returns records with real names. If the pair needs to stay generic, a record can also take type parameters, as the Java tuples article shows with Tuple<A, B>.

3. javafx.util.Pair

Many older answers point to javafx.util.Pair, which has getKey(), getValue(), equals(), hashCode() and toString(). The class belongs to JavaFX, not to the core JDK. Oracle removed JavaFX from the JDK in Java 11, so on Java 25 it needs the separate OpenJFX javafx-base dependency.

<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-base</artifactId>
    <version>25.0.4</version>
</dependency>
javafx.util.Pair<String, Integer> fx = new javafx.util.Pair<>("apple", 5);  // apple=5
String key = fx.getKey();                                          // "apple"
Integer value = fx.getValue();                                     // 5

Adding a UI toolkit to a server app only to get a pair class is not worth it, so we use Map.entry() or a record instead. The javafx.util.Pair class makes sense only in a JavaFX desktop app that already has the dependency.

4. Apache Commons Lang Pair

The Apache Commons Lang library has an abstract org.apache.commons.lang3.tuple.Pair with two subclasses, ImmutablePair and MutablePair. Many projects already have commons-lang3 on the classpath through other libraries, so this Pair costs nothing extra there.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.21.0</version>
</dependency>

The class names the two values left and right, and it also implements Map.Entry, where the key is the left value and the value is the right value. The factory method Pair.of() returns an ImmutablePair.

Pair<String, Integer> pair = Pair.of("apple", 5);                // (apple,5)
String left = pair.getLeft();                                      // "apple"
Integer right = pair.getRight();                                   // 5
String key = pair.getKey();                                        // "apple"
boolean same = pair.equals(Pair.of("apple", 5));                   // true
int order = pair.compareTo(Pair.of("banana", 3));                  // -1

The ImmutablePair class keeps both values in final fields, and its setValue() throws an exception. The MutablePair class has setters for both sides, so we use it only when the pair is reused, for example as a running total in a loop.

ImmutablePair<String, Integer> fixed = ImmutablePair.of("apple", 5);
Integer rejected = fixed.setValue(6);                              // UnsupportedOperationException
MutablePair<String, Integer> basket = MutablePair.of("apple", 5);
basket.setRight(basket.getRight() + 3);
Integer total = basket.getRight();                                 // 8

The immutability is shallow. If the pair holds a mutable object such as a List, the list can still change, so the ImmutablePair Javadoc calls the pair thread-safe only when both stored objects are thread-safe. The class is also not final, so a subclass could add mutable state. Commons Lang also has Triple, ImmutableTriple and MutableTriple for three values.

5. Vavr Tuple2

The Vavr library is a functional programming library for Java, and its Tuple2 class is a pair with operations for transforming both values. The current release is 1.0.1, and it runs on Java 8 and later.

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>1.0.1</version>
</dependency>

We create a Tuple2 with Tuple.of() and read the values with _1() and _2(). Every method that changes something returns a new Tuple2.

Tuple2<String, Integer> fruit = Tuple.of("apple", 5);            // (apple, 5)
String name = fruit._1();                                          // "apple"
Integer count = fruit._2();                                        // 5
Tuple2<Integer, String> swapped = fruit.swap();                    // (5, apple)
Tuple2<String, Integer> doubled = fruit.map2(n -> n * 2);          // (apple, 10)
Tuple2<String, Integer> renamed = fruit.update1("mango");          // (mango, 5)
String label = fruit.apply((f, n) -> n + " x " + f);               // "5 x apple"
Map.Entry<String, Integer> asEntry = fruit.toEntry();              // apple=5

The update1() and update2() methods do not change the original tuple, despite their names. Vavr also has Tuple3 up to Tuple8 for larger groups.

6. Choosing a Pair Class

The options differ in dependencies, null handling and mutability.

OptionDependencyValue namesMutableAllows nullComparable
RecordNone, Java 16+Yes, our ownNoYesOnly if we implement it
Map.entry()None, Java 9+getKey(), getValue()NoNoNo
AbstractMap.SimpleEntryNonegetKey(), getValue()Value onlyYesNo
AbstractMap.SimpleImmutableEntryNonegetKey(), getValue()NoYesNo
javafx.util.Pairjavafx-basegetKey(), getValue()NoYesNo
Commons ImmutablePaircommons-lang3getLeft(), getRight()NoYesYes
Commons MutablePaircommons-lang3getLeft(), getRight()YesYesYes
Vavr Tuple2vavr_1(), _2()NoYesYes

For a value that leaves the method, such as a service result or a DTO, we declare a record. Inside a method or a stream, Map.entry() is enough and needs no import beyond java.util. Commons Lang and Vavr pairs make sense when the project already depends on those libraries, for example to sort pairs with their built-in compareTo().

7. Pair in Java FAQs

Pair questions mostly start from the missing JDK class and from tutorials that still import the JavaFX one.

7.1. Is there a Pair class in Java?

No, not in the core JDK. The javafx.util.Pair class is part of JavaFX, which has been a separate download since Java 11. The JDK’s own two-value types are Map.Entry implementations, such as the ones returned by Map.entry().

7.2. What can replace javafx.util.Pair?

Use Map.entry() or AbstractMap.SimpleImmutableEntry for the same getKey() and getValue() methods, or a record for named values. Code that calls getKey() and getValue() keeps working with the Map.Entry types after changing the import and the constructor call.

7.3. What is the difference between a Pair and a HashMap?

A pair holds one key and one value, whereas a HashMap holds many entries with unique keys and finds a value by its key. A List of pairs can contain the same key twice and keeps the insertion order, which a HashMap does not.

7.4. How do we create a list of pairs in Java?

We create a list of Map.Entry objects or records. The list keeps duplicates and the order of the items.

List<Map.Entry<String, Integer>> basket = List.of(Map.entry("apple", 5), Map.entry("banana", 3), Map.entry("apple", 2));
int apples = basket.stream().filter(e -> e.getKey().equals("apple")).mapToInt(Map.Entry::getValue).sum();  // 7

7.5. Can a pair hold null values?

Some pair classes can. Map.entry() throws a NullPointerException for a null key or value, while AbstractMap.SimpleEntry, records, Commons Lang pairs and Vavr Tuple2 accept null. Commons Lang also has Pair.ofNonNull(), which rejects null on purpose.

8. Conclusion

Java has no general Pair class, and the JDK developers prefer named types. Since Java 16, a record is the best pair for anything that leaves a method, because its component names say what the values are.

For short-lived pairs, Map.entry() is built in, and AbstractMap.SimpleEntry covers the cases with null or a changing value. The javafx.util.Pair class needs JavaFX since Java 11, whereas Commons Lang Pair and Vavr Tuple2 fit projects that already use those libraries. The Java collections tutorial covers the maps and lists that hold these pairs.

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.