A Java array has a fixed length, so to resize an array, we create a new array with the new length and copy the elements into it, most often with Arrays.copyOf(array, newLength). The method pads a longer copy with default values and truncates a shorter one, and we assign the result back to our variable.
We resize an array when the data grows past the length we picked at the start, for example when a form gets one more color option, or when we want to drop the unused slots of a buffer after reading the data.
The following example grows and shrinks arrays with Arrays.copyOf(), with the result of each line as a comment.
String[] colors = {"red", "green", "blue"};
String[] bigger = Arrays.copyOf(colors, 5); // [red, green, blue, null, null]
bigger[3] = "pink"; // [red, green, blue, pink, null]
String[] smaller = Arrays.copyOf(colors, 2); // [red, green]
int[] scores = Arrays.copyOf(new int[] {90, 85}, 4); // [90, 85, 0, 0]
colors = Arrays.copyOf(colors, colors.length + 1); // the variable points to the new array
colors[colors.length - 1] = "white"; // [red, green, blue, white]
Notice that Arrays.copyOf() never changes the original array. It returns a new one, so the code keeps the new length only when it assigns the result to a variable, as the last two lines do.
Next, we look at why the length is fixed and which values fill the new slots. After that, we insert and remove elements in the middle with System.arraycopy() and resize a 2D array. The last section switches to an ArrayList for data that changes its size often.
1. Why an Array Cannot Change Its Length
When Java creates an array, it reserves one block of memory for all its elements and stores the length in the array object. The field length is final, so no method can make an existing array longer or shorter. “Resizing” always means creating a new array with the elements copied into it, and pointing the variable to the new array.
The last step causes a common bug. Every variable that still points to the old array keeps seeing the old length and the old elements, because the copy is a different object.
![Before the resize, the variables colors and alias both point to one array of length 3 with red, green and blue. After colors = Arrays.copyOf(colors, 4) and colors[3] = "pink", colors points to a new array of length 4, while alias still points to the old array of length 3.](https://howtodoinjava.com/wp-content/uploads/2026/10/java-resize-array-copyof-new-array-old-alias.png)
In the next snippet, alias keeps a second reference to the original array before we resize colors.
String[] colors = {"red", "green", "blue"};
String[] alias = colors;
colors = Arrays.copyOf(colors, 4);
colors[3] = "pink";
int newLength = colors.length; // 4, [red, green, blue, pink]
int oldLength = alias.length; // 3, [red, green, blue]
boolean same = alias == colors; // false
For example, a settings class passes its array of colors to a menu component and later grows the array to add one color. The menu still holds the old array, so it never shows the new color. When several objects share the data, we share a List instead of an array.
The garbage collector frees the memory of the old array only when no variable points to it any more.
2. Resize an Array in Java With Arrays.copyOf()
The method Arrays.copyOf(original, newLength) creates an array of the same type with newLength slots and copies Math.min(original.length, newLength) elements into it. It has an overload for every primitive type and a generic one for object arrays, and the result has the same class as the original, so a String[] stays a String[].
2.1. Growing an Array and the Default Values
When the new length is bigger, the extra slots get the default value of the element type. Our code has to treat those slots as empty, e.g. skip null entries when it prints the array.
| Array type | Value in the new slots |
|---|---|
| int[], long[], short[], byte[] | 0 |
| double[], float[] | 0.0 |
| boolean[] | false |
| char[] | ‘\u0000’ (the character with code 0) |
| String[] and other object arrays | null |
int[] ints = Arrays.copyOf(new int[] {1}, 3); // [1, 0, 0]
double[] doubles = Arrays.copyOf(new double[] {1.5}, 3); // [1.5, 0.0, 0.0]
boolean[] flags = Arrays.copyOf(new boolean[] {true}, 3); // [true, false, false]
char[] chars = Arrays.copyOf(new char[] {'a'}, 3); // chars[1] has the code 0
String[] texts = Arrays.copyOf(new String[] {"a"}, 3); // [a, null, null]
The copy is shallow, so for an object array, the new array points to the same objects as the old one. Changing an object through one array shows up in the other. For example, after StringBuilder[] moreNotes = Arrays.copyOf(notes, 2), a call moreNotes[0].append(“b”) also changes notes[0]. The shallow copy of arrays article shows how to make a deep copy when we need independent objects.
2.2. Shrinking an Array and Trimming Unused Slots
When the new length is smaller, Arrays.copyOf() keeps the first newLength elements and drops the rest. To keep a part from the middle or the end, we use Arrays.copyOfRange(original, from, to), where from is included and to is not, as explained in copying an array range.
String[] colors = {"red", "green", "blue", "pink"};
String[] firstTwo = Arrays.copyOf(colors, 2); // [red, green]
String[] lastTwo = Arrays.copyOfRange(colors, 2, 4); // [blue, pink]
String[] empty = Arrays.copyOf(colors, 0); // []
Trimming is useful when we fill a buffer of unknown size. A CSV import reserves 10 slots, reads only 2 values and keeps a counter of filled slots. Arrays.copyOf(buffer, count) returns an array without the empty slots at the end.
String[] buffer = new String[10];
int count = 0;
buffer[count++] = "red";
buffer[count++] = "green";
String[] trimmed = Arrays.copyOf(buffer, count); // [red, green]
2.3. Errors From Arrays.copyOf()
The method checks its arguments before copying. A negative length throws a NegativeArraySizeException, and a null array throws a NullPointerException.
String[] colors = {"red", "green"};
String[] missing = null;
String[] bad = Arrays.copyOf(colors, -1); // NegativeArraySizeException: -1
String[] none = Arrays.copyOf(missing, 2); // NullPointerException
A negative length shows up when we compute it, e.g. colors.length – removedCount with a wrong count. So we check computed lengths with Math.max(0, …) or validate them before the call.
3. Inserting and Removing Elements With System.arraycopy()
The method Arrays.copyOf() always copies from the start of the array. To add or remove an element in the middle, we also need System.arraycopy(src, srcPos, dest, destPos, length), which copies length elements from position srcPos of one array to position destPos of another. Arrays.copyOf() itself calls System.arraycopy() to copy the elements.
The following example is a pair of generic helpers. The method insertAt() creates an array one slot longer, copies the elements after the index one position to the right and writes the new element into the gap. The method removeAt() does the opposite.
static <T> T[] insertAt(T[] array, int index, T element) {
if (index < 0 || index > array.length) {
throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
}
T[] result = Arrays.copyOf(array, array.length + 1);
System.arraycopy(array, index, result, index + 1, array.length - index);
result[index] = element;
return result;
}
static <T> T[] removeAt(T[] array, int index) {
if (index < 0 || index >= array.length) {
throw new IndexOutOfBoundsException("Index: " + index + ", Length: " + array.length);
}
T[] result = Arrays.copyOf(array, array.length - 1);
System.arraycopy(array, index + 1, result, index, array.length - index - 1);
return result;
}
Both helpers check the index first, so a wrong index gives a clear message instead of an ArrayIndexOutOfBoundsException from deep inside the copy.
String[] colors = {"red", "green", "blue"};
String[] inserted = insertAt(colors, 1, "pink"); // [red, pink, green, blue]
String[] appended = insertAt(colors, colors.length, "white"); // [red, green, blue, white]
String[] removed = removeAt(inserted, 0); // [pink, green, blue]
String[] invalid = insertAt(colors, 7, "black"); // IndexOutOfBoundsException: Index: 7, Length: 3
Each call copies the whole array, so a loop that inserts one element at a time takes O(n) time per call. For more ways to delete elements, read removing items from an array.
4. Resizing a 2D Array
A 2D array in Java is an array of row arrays. Arrays.copyOf() on a 2D array resizes only the outer array, so the new array gets null rows at the end and shares the existing rows with the old array.
int[][] grid = {{1, 2}, {3, 4}};
int[][] taller = Arrays.copyOf(grid, 3); // [[1, 2], [3, 4], null]
boolean shared = taller[0] == grid[0]; // true (same row object)
To change the number of rows and columns, we copy each row with its own Arrays.copyOf() call and create new rows for the extra ones. For example, a seat map of a cinema hall grows from 2 x 2 to 3 x 3 seats when the hall gets a new row and a new column.
static int[][] resize(int[][] source, int rows, int cols) {
int[][] result = new int[rows][];
for (int r = 0; r < rows; r++) {
result[r] = r < source.length
? Arrays.copyOf(source[r], cols)
: new int[cols];
}
return result;
}
The new array has its own rows, so changing it leaves the source unchanged. The same method also shrinks the grid.
int[][] grid = {{1, 2}, {3, 4}};
int[][] resized = resize(grid, 3, 3); // [[1, 2, 0], [3, 4, 0], [0, 0, 0]]
resized[0][0] = 99;
int original = grid[0][0]; // 1
int[][] smaller = resize(grid, 1, 1); // [[1]]
We print 2D arrays with Arrays.deepToString(), as the comments show. The method Arrays.toString() would print each row as its type and hash code, such as [I@ followed by a hex number.
5. Using an ArrayList Instead of Resizing
When the number of elements changes often, we use an ArrayList instead of an array. An ArrayList keeps an array inside and grows it by 50% when it is full, so most add() calls do not copy anything. We never write the copy code ourselves, and add(index, e) and remove() shift the elements for us.
String[] colors = {"red", "green", "blue"};
List<String> list = new ArrayList<>(Arrays.asList(colors));
list.add("pink"); // [red, green, blue, pink]
list.add(0, "white"); // [white, red, green, blue, pink]
list.remove("green"); // [white, red, blue, pink]
String[] back = list.toArray(new String[0]); // [white, red, blue, pink]
The last line converts the list back to an array for an API that needs one. Notice the new ArrayList<>(…) around Arrays.asList(). The list from Arrays.asList() is a fixed-size view of the array, so its add() throws an UnsupportedOperationException, as explained in Arrays.asList() vs new ArrayList().
| Array with Arrays.copyOf() | ArrayList | |
|---|---|---|
| Length | fixed, a resize creates a new array | grows and shrinks with add() and remove() |
| Cost of adding one element at the end | O(n), copies every element | O(1) on average |
| Primitive values | int[], double[] without boxing | only objects, e.g. Integer |
| Good fit | fixed-size or primitive data, APIs that need arrays | data that grows or shrinks often |
The array vs ArrayList comparison covers more differences. If we need to see how a growable list works on the inside, the custom list implementation builds one with the same Arrays.copyOf() call.
6. Conclusion
An array in Java cannot change its length after creation. To resize it, we call Arrays.copyOf() with the new length and assign the result to the variable, which gives us a new array with default values in the extra slots or with the extra elements dropped. Every other reference to the old array keeps the old length.
For inserting or removing in the middle, we combine Arrays.copyOf() with System.arraycopy(). A 2D array needs a copy of each row, because Arrays.copyOf() copies only the outer array.
When the size changes often, an ArrayList is the better choice, since it grows its internal array for us and converts back to an array with toArray().
7. References
- Arrays JavaDoc (Java 25)
- System JavaDoc, arraycopy() (Java 25)
- Arrays chapter of the Java Language Specification (Java 25)
- ArrayList JavaDoc (Java 25)
Happy Learning !!