A Java array cannot shrink, so to remove an element from an array in Java, we either create a new array that is one element shorter and copy every other element into it, or shift the later elements one position left inside the same array and keep track of the new size. Most code uses the first approach, because the result is a normal array with the right length.
We remove array elements when a user cancels a booking from a list of slots, when a sensor reading fails validation, or when a configuration value must be dropped before the array goes to an API that takes arrays only.
The following example removes by index, by value and with Apache Commons Lang. The removeAt() method is defined in section 2.
int[] slots = {10, 20, 30, 40, 50};
int[] noIndex2 = removeAt(slots, 2); // [10, 20, 40, 50]
int[] no40 = Arrays.stream(slots).filter(v -> v != 40).toArray(); // [10, 20, 30, 50]
int[] noFirst = ArrayUtils.remove(slots, 0); // [20, 30, 40, 50]
int length = slots.length; // 5, the original is unchanged
Notice that each call returns a new array and leaves slots as it was. We start with the two removal strategies, write safe methods for removing by index and by value, compare them with the List route and the ArrayUtils methods, and close with a booking example.
1. Two Ways to Remove from a Fixed-Size Array
Java sets the length of an array when it creates the array. Removing an element therefore cannot change slots.length, and Java offers two ways around it.
- Copy into a new array of length – 1, skipping the removed position. The original array stays unchanged, and the cost is one new array per removal.
- Shift the elements after the removed position one step left in the same array, clear the last used slot and keep a separate size variable. No new array is created, but the code must ignore the slots past size.

ArrayList uses the second approach internally, which is why it is the better type when elements come and go often. The array vs ArrayList article compares both types, and resizing an array shows the opposite operation.
2. Removing an Element by Index
To remove by index, we copy the part before the index and the part after it into a new array with two System.arraycopy() calls. The method validates the index first with Objects.checkIndex(), so a bad index fails with a clear message instead of an exception from inside the copy.
static int[] removeAt(int[] array, int index) {
Objects.checkIndex(index, array.length);
int[] result = new int[array.length - 1];
System.arraycopy(array, 0, result, 0, index);
System.arraycopy(array, index + 1, result, index, array.length - index - 1);
return result;
}
int[] readings = {7, 3, 9, 4};
int[] withoutFirst = removeAt(readings, 0); // [3, 9, 4]
int[] withoutLast = removeAt(readings, 3); // [7, 3, 9]
int[] invalid = removeAt(readings, 4); // IndexOutOfBoundsException: Index 4 out of bounds for length 4
For object arrays, a generic version uses Arrays.copyOf() to create the shorter array with the same runtime type, and copies the tail with one System.arraycopy() call.
static <T> T[] removeAt(T[] array, int index) {
Objects.checkIndex(index, array.length);
T[] result = Arrays.copyOf(array, array.length - 1);
System.arraycopy(array, index + 1, result, index, array.length - index - 1);
return result;
}
String[] tasks = {"email", "lunch", "gym", "call"};
String[] done = removeAt(tasks, 1); // [email, gym, call]
3. Removing an Element by Value
Removing by value needs a decision first, whether to remove only the first match or every match. For the first match, we find its index and reuse removeAt(). When the value is missing, the method returns the original array, so the caller can compare references or lengths to see whether anything changed.
static int[] removeFirst(int[] array, int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
return removeAt(array, i);
}
}
return array;
}
int[] votes = {3, 1, 3, 2};
int[] oneLess = removeFirst(votes, 3); // [1, 3, 2]
int[] unchanged = removeFirst(votes, 9); // [3, 1, 3, 2]
To remove every match, a stream with filter() keeps the elements we want and toArray() collects them into a new array of the right length. For object arrays, Objects.equals() compares values safely even when the array contains null.
int[] marks = {3, 1, 3, 2};
int[] noThrees = Arrays.stream(marks).filter(m -> m != 3).toArray(); // [1, 2]
String[] tags = {"java", null, "draft", "draft"};
String[] published = Arrays.stream(tags).filter(t -> !Objects.equals(t, "draft")).toArray(String[]::new); // [java, null]
String[] noNulls = Arrays.stream(tags).filter(Objects::nonNull).toArray(String[]::new); // [java, draft, draft]
The same filter() call removes elements by any condition, such as readings below zero or names longer than 20 characters.
4. Shifting Elements Left in the Same Array
When creating a new array for every removal is too expensive, such as for a large buffer that changes all the time, we shift the tail left in place. A single System.arraycopy() call does the shift, because the method copies correctly even when the source and target ranges overlap.
After an in-place removal, the array length does not change, so the method must return the new number of used elements and the caller must stop reading at that size. Clearing the freed slot avoids a duplicate last value and, for object arrays, lets the garbage collector reclaim the removed object.
static int removeInPlace(int[] array, int size, int index) {
Objects.checkIndex(index, size);
System.arraycopy(array, index + 1, array, index, size - index - 1);
array[size - 1] = 0; // clear the freed slot
return size - 1;
}
int[] queue = {10, 20, 30, 40, 50};
int size = removeInPlace(queue, queue.length, 1); // 4
String raw = Arrays.toString(queue); // "[10, 30, 40, 50, 0]"
int[] used = Arrays.copyOf(queue, size); // [10, 30, 40, 50]
Without the cleanup line, the array would read [10, 30, 40, 50, 50], which is the duplicate last element that several tutorials show as the result. A size variable next to an array is what ArrayList keeps internally, so for more than a few in-place removals, an ArrayList gives the same behavior with less code.
5. Converting to a List for Several Removals
When the code removes many elements by different rules, converting the array to a List, removing, and converting back is the shortest code. The list must be a real ArrayList, because Arrays.asList() returns a fixed-size list that throws UnsupportedOperationException on remove(). The Arrays.asList() vs new ArrayList() article explains the difference.
String[] items = {"pen", "ink", "cap", "box"};
List<String> fixed = Arrays.asList(items);
boolean failed = fixed.remove("ink"); // UnsupportedOperationException
String[] items = {"pen", "ink", "cap", "box"};
List<String> list = new ArrayList<>(Arrays.asList(items));
list.remove("ink");
list.removeIf(s -> s.startsWith("c"));
String[] left = list.toArray(String[]::new); // [pen, box]
Lists of Integer have two overloads that look alike. List.remove(int) removes by index, whereas remove(Object) removes by value, and a call with an int argument picks the index version. To remove the value, we box it with Integer.valueOf() or use removeIf().
List<Integer> codes = new ArrayList<>(List.of(5, 7, 1));
Integer byIndex = codes.remove(1); // 7, removed position 1
boolean byValue = codes.remove(Integer.valueOf(1)); // true, removed value 1
List<Integer> rest = codes; // [5]
The removeIf() and remove all occurrences articles cover the list methods in more detail, and list and array conversion covers the conversions for primitive arrays.
6. ArrayUtils Removal Methods
The ArrayUtils class in Apache Commons Lang (commons-lang3 3.21.0) has five removal methods with overloads for every primitive type and for object arrays. Each one returns a new array and leaves the input unchanged.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
| Method | Removes | Missing index or value |
|---|---|---|
| remove(array, index) | The element at one index | IndexOutOfBoundsException |
| removeAll(array, indices…) | The elements at several indexes | IndexOutOfBoundsException |
| removeElement(array, value) | The first occurrence of a value | Returns a copy without changes |
| removeElements(array, values…) | One occurrence per listed value, so a value listed twice removes two | Ignores values that are not present |
| removeAllOccurrences(array, value) | Every occurrence of a value | Returns a copy without changes |
int[] nums = {1, 1, 2, 2, 3, 3, 3};
int[] r1 = ArrayUtils.remove(nums, 6); // [1, 1, 2, 2, 3, 3]
int[] r2 = ArrayUtils.removeAll(nums, 0, 2, 4); // [1, 2, 3, 3]
int[] r3 = ArrayUtils.removeElement(nums, 2); // [1, 1, 2, 3, 3, 3]
int[] r4 = ArrayUtils.removeElements(nums, 1, 3, 3); // [1, 2, 2, 3]
int[] r5 = ArrayUtils.removeAllOccurrences(nums, 3); // [1, 1, 2, 2]
int[] r6 = ArrayUtils.remove(nums, 7); // IndexOutOfBoundsException: Index: 7, Length: 7
The ArrayUtils methods save the helper code from the earlier sections. For a project that does not use commons-lang3 yet, the JDK versions are short enough that adding the dependency only for removal is not necessary.
7. Dropping Cancelled Bookings in One Pass
Say a booking service loads the IDs of the day’s bookings into an int[], and a second call returns the IDs that were cancelled. Removing each cancelled ID with removeFirst() would scan the array once per ID. Putting the cancelled IDs into a Set and filtering once keeps the work linear in the number of bookings.
int[] bookings = {501, 502, 503, 504, 505, 506};
Set<Integer> cancelled = Set.of(502, 505, 999);
int[] active = Arrays.stream(bookings).filter(id -> !cancelled.contains(id)).toArray(); // [501, 503, 504, 506]
int removed = bookings.length - active.length; // 2
The cancelled ID 999 is not in the booking array, and the filter ignores it without a special case. The same pattern removes a block list of words from user input or a set of inactive users from a report.
8. Performance of Each Way to Remove an Element from an Array in Java
Removing one element costs O(n) in every approach, because the elements after it must move. The approaches differ in memory use and in how many elements they remove per call.
| Approach | Removes | New array | Time |
|---|---|---|---|
| removeAt() with System.arraycopy() | One index | Yes | O(n) |
| removeFirst() | First match of a value | Yes | O(n) |
| Arrays.stream().filter().toArray() | All matches of a value or condition | Yes | O(n) |
| removeInPlace() with a size counter | One index | No | O(n) |
| ArrayList with removeIf() | All matches | Yes, after toArray() | O(n) |
| ArrayUtils methods | Index, indexes, values | Yes | O(n) |
Removing k elements one at a time costs O(k * n), whereas one filter() pass or one removeIf() call removes all of them in O(n). When removals are frequent and the size changes all the time, an ArrayList or an ArrayDeque is the better data structure.
9. Removing Array Elements FAQs
Most follow-up questions are about the last element, null values, loops and duplicates.
9.1. How do we remove the last element of an array?
We copy all elements except the last one with Arrays.copyOf(). An empty array has no last element, so the code checks the length first.
int[] stack = {4, 8, 15};
int[] popped = stack.length > 0 ? Arrays.copyOf(stack, stack.length - 1) : stack; // [4, 8]
9.2. Can we delete an element by setting it to null or 0?
No. The slot still exists, so length stays the same and every loop over the array still visits it. Setting a slot to null is fine only when the code treats null as an empty slot everywhere, and for primitive arrays 0 is often a valid value.
9.3. Why does removing inside a for loop skip elements?
Because shifting moves the next element into the current index, and i++ moves past it. With the in-place method, we either loop backward from the end or increment i only when nothing was removed. A stream filter() avoids the problem.
9.4. How do we remove duplicates from an array?
We keep the first occurrence of each value with Arrays.stream(array).distinct().toArray() or a LinkedHashSet. The duplicate elements article also shows how to find and count them.
9.5. Does Arrays.stream().filter() modify the original array?
No. The filter() operation returns a new stream, and toArray() creates a new array from it. The source array is never changed.
10. Conclusion
Because a Java array has a fixed length, removing an element means copying the remaining elements into a shorter array, or shifting them left in place and tracking the used size. Two System.arraycopy() calls remove an index, and filter() removes values or conditions in a single pass.
Lists help when there are many different removals, as long as we use a real ArrayList and watch the remove(int) versus remove(Object) overloads. ArrayUtils wraps the same logic in ready-made methods. When the code removes elements all the time, an ArrayList is a better fit than an array.
11. References
- System.arraycopy() (Java SE 25 API)
- Objects.checkIndex() (Java SE 25 API)
- Arrays.asList() (Java SE 25 API)
- Collection.removeIf() (Java SE 25 API)
- ArrayUtils (Apache Commons Lang 3)
Happy Learning !!