Java applications frequently need to move two related values through a method, stream pipeline, cache, or data transformation. You may need to return a user ID with a score, associate a filename with its size, or carry minimum and maximum values together. Understanding how to create and use pairs efficiently is therefore an important skill for aspiring Java developers. Through H2K Infosys Java Full Stack Developer Training, learners can strengthen their knowledge of core Java, collections, generics, streams, Pairs in Java, Spring Boot, databases, and other technologies required to build complete, production-ready applications.
In many programming languages, a built-in tuple handles this requirement. Java, however, does not define a universal, general-purpose Pair type in the core language. Developers must instead choose from standard-library classes, external libraries, or custom data structures.
That absence is not necessarily a limitation. Named domain types are often clearer than anonymous containers. Still, pairs are useful when the relationship is small, temporary, and obvious from context.
The key is choosing an implementation that matches the pair’s purpose, mutability requirements, dependency constraints, and expected lifetime.
This guide examines five practical ways to create and use pairs in Java.
What Is a Pairs in Java ?
A Pairs in Java is a container that stores exactly two associated values, usually with independent generic types:
Pair<String, Integer> result = ...;

The first value might be a label, key, coordinate, or object. The second might be a count, status, measurement, or calculated result.
Pairs are particularly useful when:
- a method logically returns two results;
- a stream operation must preserve an item and its derived value;
- two values must remain associated during sorting or transformation;
- creating a complete domain class would add more ceremony than meaning.
Pairs become problematic when names such as first, second, left, and right obscure important business meaning. For long-lived APIs, types such as CustomerBalance, DateRange, or SearchResult are usually more expressive than Pair<Customer, BigDecimal>.
1. Use Map.entry() for Lightweight Immutable Pairs
Since Java 9, Map.entry(key, value) has offered one of the shortest standard-library approaches for creating an independent key-value pair.
import java.util.Map;
Map.Entry<String, Integer> score = Map.entry("Asha", 92);
System.out.println(score.getKey()); // Asha
System.out.println(score.getValue()); // 92
The returned entry is unmodifiable. Calling setValue() throws an UnsupportedOperationException.
Entries created through Map.entry() also reject null keys and values and are not serializable. Oracle defines them as value-based objects, meaning developers should not depend on their object identity or use them as synchronization locks.
This approach works particularly well with Map.ofEntries():
Map<String, String> settings = Map.ofEntries(
Map.entry("region", "ap-south"),
Map.entry("mode", "production"),
Map.entry("retry", "3")
);
It is also convenient in stream pipelines:
List<Map.Entry<String, Integer>> lengths = names.stream()
.map(name -> Map.entry(name, name.length()))
.toList();
Each element now preserves the original name and its calculated length. The results can be filtered, sorted, or collected into another structure.
Use Map.entry() when:
- the pair is temporary;
- both components must be non-null;
- immutability is desirable;
- you want to avoid an external dependency.
Avoid it when the pair must be serialized, contain null, or expose names more descriptive than key and value.
2. Use AbstractMap.SimpleEntry or SimpleImmutableEntry
The JDK also provides two concrete implementations of Map.Entry:
AbstractMap.SimpleEntry<K,V>, whose value can be changed;AbstractMap.SimpleImmutableEntry<K,V>, whose key and value references cannot be replaced.
import java.util.AbstractMap;
import java.util.Map;
Map.Entry<String, Integer> mutable =
new AbstractMap.SimpleEntry<>("attempts", 1);
mutable.setValue(2);
Map.Entry<String, Integer> fixed =
new AbstractMap.SimpleImmutableEntry<>("maxRetries", 5);
SimpleEntry allows its value to be replaced using setValue(). The key remains fixed.
SimpleImmutableEntry does not support changing either reference. Both classes are independent of a backing map and implement Serializable.
The word “immutable” requires some qualification. SimpleImmutableEntry is shallowly immutable. The key and value references cannot be replaced, but the objects referenced by them may still be mutable.
Consider the following example:
List<String> tags = new ArrayList<>();
tags.add("java");
Map.Entry<String, List<String>> entry =
new AbstractMap.SimpleImmutableEntry<>("topics", tags);
tags.add("collections");
System.out.println(entry.getValue());
// [java, collections]
The entry still references the same list, but the contents of that list have changed.
These classes are useful when an API consumes Map.Entry, but Map.entry() is too restrictive for example, when serialization, nullable components, or controlled value mutation is required.
Use these classes when:
- you want a JDK-only solution;
- the pair must be serializable;
- one or both values may be
null; - an API expects
Map.Entry.
If both components must change together, a mutable third-party pair or custom class will be more appropriate.
3. Use javafx.util.Pair in JavaFX Applications
JavaFX includes a dedicated javafx.util.Pair<K,V> class.
import javafx.util.Pair;
Pair<String, Double> product =
new Pair<>("Mechanical Keyboard", 89.99);
String name = product.getKey();
double price = product.getValue();
The API is straightforward and includes implementations of equals(), hashCode(), and toString(). OpenJFX describes it as a convenience class for representing name-value pairs.
A UI-oriented use case might associate menu labels with actions:
List<Pair<String, Runnable>> menuActions = List.of(
new Pair<>("Refresh", controller::refresh),
new Pair<>("Export", controller::export),
new Pair<>("Close", stage::close)
);
menuActions.forEach(action ->
System.out.println("Menu item: " + action.getKey())
);
The principal concern is architectural. JavaFX is distributed separately from the base JDK. Adding JavaFX solely to obtain a pair implementation introduces unnecessary dependency weight, especially in server applications, command-line utilities, and reusable libraries.
In an existing JavaFX desktop application, however, the dependency is already present. Using javafx.util.Pair for local data handling can therefore be reasonable.
Use JavaFX Pair when:
- your project already depends on JavaFX;
- the components naturally read as key and value;
- the pair remains an internal implementation detail.
Do not add JavaFX to a non-JavaFX project merely for this class.
4. Use Apache Commons Lang Pairs in Java
Apache Commons Lang provides one of the most widely recognized Java pair APIs:
import org.apache.commons.lang3.tuple.Pair;
Pair<String, Integer> score = Pair.of("Ravi", 87);
System.out.println(score.getLeft());
System.out.println(score.getRight());
Pair<L,R> implements Map.Entry<L,R>, Comparable<Pair<L,R>>, and Serializable.
Its API supports both tuple-oriented methods—getLeft() and getRight() and mapping-oriented methods—getKey() and getValue(). The library also provides ImmutablePair and MutablePair implementations.
For Maven projects, Commons Lang can be added as follows:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
Apache lists version 3.20.0 as the current downloadable release at the time of writing.
Commons Pairs in Java is particularly convenient in stream processing:
List<Pair<String, Integer>> ranked = names.stream()
.map(name -> Pair.of(name, name.length()))
.sorted((a, b) ->
Integer.compare(b.getRight(), a.getRight()))
.toList();
This pipeline associates every name with its length and then sorts the pairs by the right-hand value.
Because Pair implements Map.Entry, it can also integrate with collectors and APIs that expect entries:
Map<String, Integer> lengthByName = names.stream()
.map(name -> Pair.of(name, name.length()))
.collect(Collectors.toMap(
Pair::getLeft,
Pair::getRight
));
Pair.of(left, right) permits null values. When both components must be present, Pair.ofNonNull(left, right) provides explicit null validation.
Use Apache Commons Pairs in Java when:
- Commons Lang is already installed;
- you need immutable and mutable variants;
- serialization or
Map.Entrycompatibility matters; - left/right terminology suits the operation.
Avoid adding an entire dependency when one small internal record would provide equivalent functionality.
5. Create a Custom Generic Record
For modern Pairs in Java applications, a record is often the cleanest solution:
public record Pair<L, R>(L first, R second) {}
The pair can then be created and accessed directly:
Pair<String, Integer> result =
new Pair<>("completed", 42);
System.out.println(result.first());
System.out.println(result.second());
Records became a permanent Pairs in Java feature in Java 16. The compiler automatically generates a canonical constructor, accessor methods, equals(), hashCode(), and toString(). Record components are final, making records concise, shallowly immutable data carriers.
The major advantage is control. Instead of creating a generic pair, you can choose meaningful component and type names:
public record Coordinate(
double latitude,
double longitude
) {}
public record MinMax<T>(
T minimum,
T maximum
) {}
public record SearchHit<T>(
T item,
double relevance
) {}
These records communicate intent much more effectively than generic pairs.
Records can also validate their components:
public record Range(int start, int end) {
public Range {
if (start > end) {
throw new IllegalArgumentException(
"start must not exceed end"
);
}
}
}
For internal infrastructure, a generic Pair<L,R> record may be sufficient. For public APIs and business logic, a named record is normally the better design because callers do not need to remember what first() and second() represent.
Use a custom record when:
- the project uses Java 16 or later;
- you want zero external dependencies;
- strong value semantics are useful;
- meaningful component names improve readability.
Records are less suitable when supporting older Java versions or frameworks that require conventional mutable JavaBeans.
Quick Comparison

| Approach | Mutable | Allows null | Extra dependency | Best use |
|---|---|---|---|---|
Map.entry() | No | No | No | Temporary immutable pairs |
SimpleEntry | Value only | Yes | No | Serializable or mutable entries |
JavaFX Pair | No | Yes | JavaFX | Existing JavaFX applications |
Commons Pair | Optional | Yes | Commons Lang | Rich tuple utilities |
| Custom record | No | Yes by default | No | Maintainable application models |
Practical Guidelines for Efficient Pair Usage
Prefer immutability
Immutable pairs are safer in streams, caches, asynchronous operations, and concurrent code because their references cannot change unexpectedly.
Remember that shallow immutability does not protect mutable objects stored inside a pair.
Avoid pairs as universal return types
A Pairs in Java is efficient only while its meaning remains obvious. If consumers need comments or documentation to remember which side contains which value, create a named record or class.
Compare these signatures:
Pair<String, BigDecimal> calculateBalance();
CustomerBalance calculateBalance();
The second version communicates substantially more information.
Match accessor names to the relationship
Use getKey() and getValue() for genuine mappings. Use getLeft() and getRight() for neutral tuple operations. Use domain names such as latitude(), longitude(), minimum(), or maximum() when the values have stable business meaning.
Do not confuse a pair with a map
A Pairs in Java stores two associated values. A map stores key-value mappings and enforces key semantics.
Creating a map merely to return two unrelated results usually introduces more complexity than necessary.
Conclusion
Pairs in Java offers several effective pair patterns despite not having a universal built-in Pair type.
Use Map.entry() for concise, immutable key-value data. Choose SimpleEntry or SimpleImmutableEntry when you need JDK-only flexibility or serialization. Use JavaFX Pair inside existing JavaFX systems and Apache Commons Pair when your project benefits from its mature tuple API. Learning these implementation options through full stack Java developer training can help developers strengthen their understanding of core Java, collections, generics, streams, and application design.
For most modern Pairs in Java code, records provide the strongest balance of simplicity, type safety, and readability.
The general rule is straightforward: use a generic Pairs in Java for temporary implementation details, but create a named record when the values cross an API boundary or carry meaningful domain information. This approach keeps Java code compact without sacrificing maintainability.























