To concatenate two arrays in Java, we create a new array whose length is the sum of both lengths, and copy the elements of the first array followed by the elements of the second array into it. A Java array has a fixed length, so no method can append to an existing array, and every technique in this article returns a new array.
We concatenate arrays when we build a command line from default and user arguments, merge the results of two lookups, or join two byte buffers before writing them to a file or a socket.
The following example joins two int arrays with System.arraycopy() and two String arrays with a stream.
int[] first = {1, 2, 3};
int[] second = {4, 5};
int[] merged = Arrays.copyOf(first, first.length + second.length); // [1, 2, 3, 0, 0]
System.arraycopy(second, 0, merged, first.length, second.length);
String result = Arrays.toString(merged); // "[1, 2, 3, 4, 5]"
String[] fruits = {"apple", "kiwi"};
String[] veggies = {"pea"};
String[] basket = Stream.concat(Arrays.stream(fruits), Arrays.stream(veggies)).toArray(String[]::new); // [apple, kiwi, pea]
Notice that System.arraycopy() writes the second array at position first.length, right after the last element of the first array. We also cover a reusable generic method, joining more than two arrays, the one-liners from Apache Commons Lang and Guava, and the exceptions that show up with null or mixed-type arrays.
1. Why Joining Arrays Creates a Third Array
The length of a Java array is set when the array is created and never changes. To join first and second, we allocate a result array of first.length + second.length elements and copy both arrays into it, one after the other.

Both source arrays stay unchanged, and the result is an independent array. When the code appends to the same data again and again, such as rows read from a file, an ArrayList is the better fit, because it grows on its own; the array vs ArrayList comparison covers that trade-off, and resizing an array shows what growing an array by hand costs.
2. Concatenating Arrays with System.arraycopy()
The System.arraycopy() method copies a range of elements from one array into another in a single call. It works for every primitive type and for object arrays, so it is the general-purpose tool for joining arrays.
| Parameter | Meaning | Value for the second copy |
|---|---|---|
| src | Source array | second |
| srcPos | First index to read in the source | 0 |
| dest | Target array | merged |
| destPos | First index to write in the target | first.length |
| length | Number of elements to copy, not an end index | second.length |
The method throws IndexOutOfBoundsException when a range does not fit, and ArrayStoreException when the element types do not match, for example when we copy an int[] into a long[].
static int[] concat(int[] first, int[] second) {
int[] result = new int[first.length + second.length];
System.arraycopy(first, 0, result, 0, first.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
int[] scores = concat(new int[]{7, 9}, new int[]{4}); // [7, 9, 4]
int[] same = concat(new int[]{7, 9}, new int[0]); // [7, 9]
2.1. A Generic Method for Object Arrays
For object arrays, we write the method once with a type parameter. The call to Arrays.copyOf() creates the result with the runtime type of the first array, so a String[] input returns a real String[], not an Object[].
static <T> T[] concat(T[] first, T[] second) {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
String[] team = concat(new String[]{"Lokesh", "Alex"}, new String[]{"Maria"}); // [Lokesh, Alex, Maria]
Integer[] boxed = concat(new Integer[]{1}, new Integer[]{2, 3}); // [1, 2, 3]
The result array has the type of the first argument, so mixing types fails at runtime with ArrayStoreException. When we pass a String[] and an Object[], the compiler infers T as Object, but the result is a String[] that cannot store an Integer.
Object[] mixed = concat(new String[]{"a"}, new Object[]{1}); // ArrayStoreException
Object[] safe = concat(new Object[]{"a"}, new Object[]{1}); // [a, 1]
2.2. Joining More Than Two Arrays
A varargs parameter lets one method join any number of arrays. The method sums the lengths first, allocates the result once and copies each array at a running offset.
static int[] concatAll(int[]... arrays) {
int total = 0;
for (int[] array : arrays) {
total += array.length;
}
int[] result = new int[total];
int offset = 0;
for (int[] array : arrays) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
int[] week = concatAll(new int[]{1, 2}, new int[]{3}, new int[]{4, 5, 6}); // [1, 2, 3, 4, 5, 6]
int[] nothing = concatAll(); // []
3. Merging Arrays with Java Streams
Streams join arrays in one expression. The Stream.concat() method creates a stream with all elements of the first stream followed by all elements of the second, and toArray(String[]::new) collects them into an array of the right type.
String[] morning = {"tea", "toast"};
String[] evening = {"soup"};
String[] meals = Stream.concat(Arrays.stream(morning), Arrays.stream(evening)).toArray(String[]::new); // [tea, toast, soup]
For primitive arrays, IntStream.concat(), LongStream.concat() and DoubleStream.concat() avoid boxing. The JDK has no stream type for char, byte, short or float, so for those arrays we use System.arraycopy().
int[] a = {1, 2};
int[] b = {3, 4};
int[] ints = IntStream.concat(Arrays.stream(a), Arrays.stream(b)).toArray(); // [1, 2, 3, 4]
double[] prices = DoubleStream.concat(DoubleStream.of(1.5), DoubleStream.of(2.0)).toArray(); // [1.5, 2.0]
The Stream.concat() method accepts only two streams. To join three or more arrays, we stream the arrays themselves and flatten them with flatMap(), which also avoids the deep call chains that the Javadoc warns about for repeated concat() calls.
// does not compile: concat() accepts only two streams
String[] all = Stream.concat(Arrays.stream(a1), Arrays.stream(a2), Arrays.stream(a3)).toArray(String[]::new);
String[] a1 = {"x"};
String[] a2 = {"y", "z"};
String[] a3 = {"w"};
String[] all = Stream.of(a1, a2, a3).flatMap(Arrays::stream).toArray(String[]::new); // [x, y, z, w]
int[] nums = Stream.of(new int[]{1}, new int[]{2, 3}).flatMapToInt(Arrays::stream).toArray(); // [1, 2, 3]
A stream pipeline also lets us filter or transform while joining, for example distinct() to drop duplicates or sorted() to order the result. For a plain copy, streams add object allocation and per-element calls that System.arraycopy() does not have, so in a hot loop over large arrays the copy method is the better choice. The stream and array conversions article covers toArray() in more depth.
4. Copying Element by Element in a for Loop
A manual loop does the same work as System.arraycopy(), one element at a time, with a write index that keeps growing across both arrays. We write the loop when the copy needs a condition, such as skipping null entries.
String[] left = {"red", null, "blue"};
String[] right = {"green"};
String[] colors = new String[left.length + right.length];
int count = 0;
for (String c : left) {
if (c != null) {
colors[count++] = c;
}
}
for (String c : right) {
colors[count++] = c;
}
String[] trimmed = Arrays.copyOf(colors, count); // [red, blue, green]
Because the loop skipped one element, the result is shorter than the sum of both lengths, and Arrays.copyOf() cuts off the unused slot at the end.
5. One-Liners from Apache Commons Lang and Guava
Projects that already use one of these libraries get a single method call for the join. Both libraries copy with System.arraycopy() internally, so the speed is the same as our own method. The current versions are commons-lang3 3.21.0 and Guava 33.7.2-jre.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
The ArrayUtils.addAll() method has overloads for every primitive type and for object arrays. It also handles null input, which our own methods do not. When one argument is null, it returns a copy of the other one, and when both are null, it returns null.
int[] joined = ArrayUtils.addAll(new int[]{1, 2}, 3, 4); // [1, 2, 3, 4]
String[] names = ArrayUtils.addAll(new String[]{"Ann"}, "Bob", "Cid"); // [Ann, Bob, Cid]
int[] missing = null;
int[] fromNull = ArrayUtils.addAll(missing, new int[]{5, 6}); // [5, 6]
Guava has a concat() method in each primitive helper class, such as Ints, Longs and Bytes, and each one accepts any number of arrays. For object arrays, ObjectArrays.concat() needs the element class to create the result.
int[] gInts = Ints.concat(new int[]{1}, new int[]{2}, new int[]{3}); // [1, 2, 3]
String[] gWords = ObjectArrays.concat(new String[]{"a"}, new String[]{"b"}, String.class); // [a, b]
6. Joining Default and User Arguments for a Process
Say a build tool starts a Java process with a fixed set of JVM options and adds the arguments that the user typed. The ProcessBuilder constructor accepts a String varargs, so we join both arrays into one command. The user part may be missing, so the code replaces a null array with an empty one before the join.
static String[] buildCommand(String[] defaults, String[] userArgs) {
String[] extra = Objects.requireNonNullElse(userArgs, new String[0]);
String[] command = Arrays.copyOf(defaults, defaults.length + extra.length);
System.arraycopy(extra, 0, command, defaults.length, extra.length);
return command;
}
String[] defaults = {"java", "-Xmx512m"};
String[] userArgs = {"-jar", "report.jar"};
List<String> command = new ProcessBuilder(buildCommand(defaults, userArgs)).command(); // [java, -Xmx512m, -jar, report.jar]
String[] onlyDefaults = buildCommand(defaults, null); // [java, -Xmx512m]
The defaults come first because the order of the arguments matters to the java launcher. Notice that the join keeps the order of both arrays, so the options stay in front of -jar.
7. Picking a Way to Concatenate Two Arrays in Java
All the techniques copy every element once, so each one takes O(n + m) time and allocates one result array. They differ in which array types they accept, whether they need a library and how they treat null.
| Technique | Array types | More than two arrays | Null input | Dependency |
|---|---|---|---|---|
| System.arraycopy() + Arrays.copyOf() | All primitive and object arrays | With a varargs method | NullPointerException | None |
| Stream.concat() | Object arrays | Use Stream.of().flatMap() | NullPointerException | None |
| IntStream/LongStream/DoubleStream.concat() | int, long, double | Use flatMapToInt() and similar | NullPointerException | None |
| for loop | All | With nested loops | NullPointerException | None |
| ArrayUtils.addAll() | All primitive and object arrays | No, two at a time | Returns a copy of the other array | commons-lang3 |
| Ints.concat(), ObjectArrays.concat() | Each primitive type, object arrays | Yes for primitives | NullPointerException | Guava |
In application code without these libraries, the generic concat() method from section 2 is the most flexible choice. Streams read better when the join is part of a pipeline with filter(), map() or distinct().
8. Array Concatenation FAQs
Duplicates, mixed types and copies are the questions that come up after the first working join.
8.1. How do we concatenate two arrays and remove duplicates?
We join them with a stream and add distinct(), which keeps the first occurrence of each value in its original order. That result is the union of both arrays, which the union of two arrays article covers with sets as well.
int[] x1 = {1, 2, 3};
int[] x2 = {3, 4, 1};
int[] union = IntStream.concat(Arrays.stream(x1), Arrays.stream(x2)).distinct().toArray(); // [1, 2, 3, 4]
8.2. Can we concatenate an int array and a long array?
No, not in one step. System.arraycopy() throws ArrayStoreException when the primitive types differ, and the stream classes do not mix. We widen the int values first, for example with Arrays.stream(ints).asLongStream(), and join two LongStream objects.
8.3. How do we append a single element to an array?
We copy the array into one that is one element longer and set the last slot. The same idea with a larger new length is how we resize an array.
int[] base = {1, 2};
int[] longer = Arrays.copyOf(base, base.length + 1);
longer[base.length] = 3;
String appended = Arrays.toString(longer); // "[1, 2, 3]"
8.4. Does concatenation copy the objects inside the arrays?
No. For object arrays, the result holds the same references as the source arrays, so changing an object through one array is visible through the other. That is a shallow copy, the same as the clone() of an array.
8.5. Which is the fastest way to concatenate arrays in Java?
System.arraycopy(), alone or through Arrays.copyOf(), because it copies a whole range in one call that the JIT compiles to a bulk memory copy. The libraries use it internally. Streams add setup and per-element overhead, which matters only for large arrays in a hot loop.
9. Conclusion
Every way to concatenate two arrays in Java allocates a new array of the combined length and copies both arrays into it. Arrays.copyOf() plus one System.arraycopy() call does it for any array type, and a generic method keeps the type of the first array.
Streams join object arrays and int, long or double arrays in one expression and can drop duplicates on the way. For one-liners, ArrayUtils.addAll() and Guava’s concat() methods do the same copy behind a single call, and only ArrayUtils.addAll() accepts null arrays.
10. References
- System.arraycopy() (Java SE 25 API)
- Arrays.copyOf() (Java SE 25 API)
- Stream.concat() (Java SE 25 API)
- IntStream.concat() (Java SE 25 API)
- ArrayUtils (Apache Commons Lang 3)
- Ints (Guava API)
Happy Learning !!