The union of two arrays in Java is a new array that holds every distinct value found in either array, so a value that appears in both arrays, or twice in one of them, appears only once in the result. Java has no union() method for arrays, so we build it with a Set or with a stream that calls distinct().
We need a union whenever two sources describe the same kind of thing and we want each item once, such as the merged recipients of two mailing lists, the tags of two blog posts, or the permissions a user gets from two roles.
The following example combines two int arrays with IntStream and keeps the values in the order they first appear.
int[] a = {1, 3, 5, 3};
int[] b = {3, 4, 5};
int[] union = IntStream.concat(Arrays.stream(a), Arrays.stream(b)).distinct().toArray(); // [1, 3, 5, 4]
Notice that 3 and 5 are in the result once, although they appear three and two times in the input. The sections that follow cover object arrays with LinkedHashSet, sorted input without any set, unions of many arrays, custom objects, and a comparison of the approaches.
1. Union vs Concatenation of Arrays
A union follows the rule from set theory, where each value is either in the result or not, so the result has no duplicates. Concatenation is a different operation. It copies all elements of the first array and all elements of the second array into one longer array and keeps every repeat.
![Concatenation of [1, 3, 5, 3] and [3, 4, 5] gives seven elements while the union gives [1, 3, 5, 4]](https://howtodoinjava.com/wp-content/uploads/2026/10/java-union-of-two-arrays.png)
The difference matters in tests, because two arrays without common or repeated values give the same result for both operations. When the inputs overlap, the concatenated array is longer and contains the shared values twice.
int[] first = {1, 3, 5, 3};
int[] second = {3, 4, 5};
int[] joined = IntStream.concat(Arrays.stream(first), Arrays.stream(second)).toArray(); // [1, 3, 5, 3, 3, 4, 5]
int[] union = IntStream.concat(Arrays.stream(first), Arrays.stream(second)).distinct().toArray(); // [1, 3, 5, 4]
When we need all elements, including repeats, we concatenate the arrays instead.
2. Union of Two Arrays of Objects with LinkedHashSet
A Set rejects a value that it already contains, which makes it the natural container for a union. We add the elements of the first array, call addAll() with the second array, and convert the set back to an array.
The choice of set decides the order of the result. A HashSet makes no promise about iteration order, whereas a LinkedHashSet returns the elements in the order they were first added, and a TreeSet sorts them.
For example, a training app records who joined the morning session and who joined the evening session, and the report lists each attendee once, in the order they first showed up.
String[] morning = {"ana", "raj", "li"};
String[] evening = {"li", "tom", "ana"};
Set<String> attendees = new LinkedHashSet<>(Arrays.asList(morning));
boolean changed = attendees.addAll(Arrays.asList(evening)); // true, "tom" was added
String[] union = attendees.toArray(String[]::new); // [ana, raj, li, tom]
Set<String> sorted = new TreeSet<>(attendees); // [ana, li, raj, tom]
The method addAll() returns true when the set changed, so it also tells us whether the second array added anything new. The call toArray(String[]::new) returns a String[] instead of an Object[], and it has been in the Collection interface since Java 11.
A wrapper array such as Integer[] works the same way. A primitive int[] does not, because Arrays.asList() treats the whole int[] as a single element of a List<int[]>.
3. Union of int Arrays with IntStream
For primitive arrays, a stream avoids the detour through wrapper objects. The method IntStream.concat() joins two streams, and distinct() drops every value it has already passed. Because the stream is ordered, distinct() keeps the first occurrence of each value, so the result has the same order as a LinkedHashSet.
A varargs method accepts any number of arrays, so the same code handles two arrays or ten. The method flatMapToInt() turns the stream of arrays into one IntStream of their elements.
static int[] union(int[]... arrays) {
return Arrays.stream(arrays)
.flatMapToInt(Arrays::stream)
.distinct()
.toArray();
}
int[] two = union(new int[]{4, 2, 4}, new int[]{2, 9}); // [4, 2, 9]
int[] three = union(new int[]{1}, new int[]{2, 1}, new int[]{3}); // [1, 2, 3]
int[] sortedUnion = IntStream.of(union(new int[]{8, 2}, new int[]{5, 2})).sorted().toArray(); // [2, 5, 8]
int[] none = union(); // []
Calling sorted() after distinct() gives the union in ascending order. The same pattern works for long[] and double[] with flatMapToLong() and flatMapToDouble().
4. Union of Two Sorted Arrays with Two Pointers
When both arrays are already sorted, we can build the union without any set. Two index variables walk through the arrays at the same time, the way the merge step of merge sort does, and the smaller of the two current values goes to the result. When both values are equal, the value is copied once and both indexes move forward.
The result stays sorted, so a repeated value can only sit next to its previous copy. Comparing each value with the last one written is enough to skip duplicates, including repeats inside one array.
static int[] unionOfSorted(int[] a, int[] b) {
int[] out = new int[a.length + b.length];
int i = 0, j = 0, k = 0;
while (i < a.length || j < b.length) {
int next;
if (j == b.length || (i < a.length && a[i] < b[j])) {
next = a[i++];
} else if (i == a.length || b[j] < a[i]) {
next = b[j++];
} else { // equal values, take one
next = a[i++];
j++;
}
if (k == 0 || out[k - 1] != next) {
out[k++] = next; // skip repeats
}
}
return Arrays.copyOf(out, k);
}
int[] merged = unionOfSorted(new int[]{1, 2, 2, 7}, new int[]{2, 3, 9}); // [1, 2, 3, 7, 9]
int[] oneEmpty = unionOfSorted(new int[]{}, new int[]{4, 4, 6}); // [4, 6]
The method reads each element once, so it runs in O(n + m) time and needs no hashing. It returns wrong results for unsorted input, so we use it only when the arrays come sorted, for example from a database query with ORDER BY. Sorting two unsorted arrays first costs O(n log n + m log m), which is slower than the hash-based approaches.
5. Merging Role Permissions from Several Arrays
Say a content management app stores the permissions of each role as a String[]. A user with the roles editor and reviewer gets every permission of both roles, and the admin page shows each permission once, in a stable order.
String[] editor = {"read", "write", "publish"};
String[] reviewer = {"read", "comment"};
String[] perms = Stream.of(editor, reviewer).flatMap(Arrays::stream).distinct().toArray(String[]::new); // [read, write, publish, comment]
boolean canComment = Arrays.asList(perms).contains("comment"); // true
For a permission check that runs on every request, we keep the union as a Set<String> instead of an array, because contains() on a HashSet or LinkedHashSet answers in constant time. Converting to an array only makes sense when an API expects one.
5.1. Union of Custom Objects
Sets and distinct() use equals() and hashCode() to find duplicates. A record gets both methods from its components, so two records with the same values count as one element. A plain class without these methods compares object identity, and the union keeps every object.
record Tag(String name) {}
Tag[] postA = {new Tag("java"), new Tag("arrays")};
Tag[] postB = {new Tag("arrays"), new Tag("streams")};
Tag[] tags = Stream.concat(Arrays.stream(postA), Arrays.stream(postB)).distinct().toArray(Tag[]::new); // [Tag[name=java], Tag[name=arrays], Tag[name=streams]]
To treat “Java” and “java” as the same value, we collect into a TreeSet with String.CASE_INSENSITIVE_ORDER. A TreeSet compares with its comparator instead of equals(), and it keeps the first spelling it receives.
Set<String> labels = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
Collections.addAll(labels, "Java", "Spring");
Collections.addAll(labels, "java", "JUnit");
String[] caseless = labels.toArray(String[]::new); // [Java, JUnit, Spring]
6. Choosing a Union Approach
For most code, a LinkedHashSet for object arrays and distinct() for primitive arrays give a correct union in O(n + m) time with a predictable order. The two-pointer merge pays off only when the input is sorted, and a TreeSet when the result must be sorted.
| Approach | Input | Result order | Time | Extra memory |
|---|---|---|---|---|
| HashSet + addAll() | Object arrays | Not defined | O(n + m) | O(n + m) |
| LinkedHashSet + addAll() | Object arrays | First appearance | O(n + m) | O(n + m) |
| TreeSet | Object arrays | Sorted | O((n + m) log(n + m)) | O(n + m) |
| IntStream + distinct() | Primitive arrays | First appearance | O(n + m) | O(n + m) |
| Two pointers | Sorted arrays | Sorted | O(n + m) | Only the result |
| Nested loops | Any | First appearance | O((n + m)2) | Only the result |
The nested-loop version, which checks each value against the result built so far, still shows up in interview answers. It is fine for a handful of elements, but with 10,000 elements per array it makes up to 200 million comparisons.
7. Array Union FAQs
Duplicates, sorted input and lists come up as soon as the first union works.
7.1. Does the union of two arrays remove duplicates?
Yes. A union contains each distinct value once, including values repeated inside one array. If we want all elements with their repeats, we need concatenation instead, as section 1 shows.
7.2. How do we find the union of two arrays without using a Set?
We sort both arrays and merge them with two pointers, as in section 4. Another option is IntStream.distinct(), which uses a set internally but keeps our code free of one.
7.3. How do we get the union of two lists instead of arrays?
We create a LinkedHashSet from the first list and call addAll() with the second list. If duplicates should stay, we merge the ArrayLists with addAll() on a new ArrayList.
7.4. How do we find the elements that are in only one of the two arrays?
We take the union and remove the common elements, which gives the symmetric difference. The common part is the intersection of the two arrays, and removeAll() takes it out of the union set.
Set<Integer> onlyOne = new LinkedHashSet<>(List.of(1, 2, 3));
onlyOne.addAll(List.of(2, 3, 4));
boolean removed = onlyOne.removeAll(Set.of(2, 3)); // true
String result = onlyOne.toString(); // "[1, 4]"
8. Conclusion
A union keeps each distinct value of both arrays once, which is what separates it from concatenation. For object arrays, a LinkedHashSet with addAll() gives the union in first-seen order, and a TreeSet gives it sorted.
For int[] and other primitive arrays, IntStream.concat() or flatMapToInt() with distinct() avoids boxing and handles any number of arrays. When both arrays are sorted, the two-pointer merge produces a sorted union in one pass without a set.
9. References
- LinkedHashSet (Java SE 25 API)
- IntStream (Java SE 25 API)
- Stream.distinct() (Java SE 25 API)
- TreeSet (Java SE 25 API)
Happy Learning !!