Array vs ArrayList in Java: Differences and When to Use Each

Array vs ArrayList in Java compared on size, capacity growth, boxing, type safety and Big-O cost, with a decision table and one-line conversions.

An int array storing values in its slots next to an ArrayList of Integer whose internal Object array stores references and has spare capacity

An array in Java has a fixed length that we set when we create it, whereas ArrayList is a class from the Collections Framework that grows as we add elements, because it copies its elements into a bigger internal array whenever the current one is full. Arrays can hold primitives such as int, while an ArrayList holds only objects.

We use an array when the number of elements is known up front or when we work with large amounts of primitive data, such as pixels or sensor readings. We use an ArrayList for most other lists in application code, such as the items in a shopping cart, because it can add, remove and search elements with ready-made methods.

The following example creates both and asks each one for its size.

int[] steps = new int[7];                 // 7 days, all 0
steps[0] = 8200;
int days = steps.length;                  // 7
List<String> cart = new ArrayList<>();
cart.add("milk");
cart.add("bread");
int items = cart.size();                  // 2

Notice that an array reports its fixed capacity through the length field, while an ArrayList reports the number of elements it holds through the size() method. We compare the two side by side, look inside an ArrayList to see how it grows, check boxing and type safety, and finish with a decision table and the conversions between them.

1. Array vs ArrayList in Java, Side by Side

An array is part of the Java language itself. It has its own syntax with square brackets, a length field, and a type such as int[] or String[]. An ArrayList is an ordinary class that implements the List interface, and inside it keeps an Object[] named elementData plus a counter of the used slots.

An int array storing values in its slots next to an ArrayList of Integer whose internal Object array stores references and has spare capacity
An int[] stores the values themselves, an ArrayList stores references to Integer objects and keeps spare slots for future add() calls

Most differences follow from that design, a language-level container with a fixed length on one side and a class that manages such a container for us on the other.

FeatureArrayArrayList
SizeFixed when createdGrows on add()
Element countlength field (capacity)size() method (used slots)
PrimitivesYes, int[], double[] and so onNo, only wrapper objects such as Integer
Type checkAt runtime, ArrayStoreExceptionAt compile time through generics
Syntaxa[i], a[i] = vget(i), set(i, v)
Add and removeNot possible, we copy into a new arrayadd(), remove(), removeIf()
Helper methodsStatic methods in java.util.ArraysInstance methods plus Collections and streams
Multiple dimensionsint[][]List<List<T>>
toString()Type and hash code, such as [I@1b6d3586Elements, such as [milk, bread]
Thread safetyNoneNone, not synchronized

Both give constant-time access by index, because an ArrayList reads from its internal array. Neither is thread-safe, so concurrent writers need a lock or a concurrent collection in both cases.

2. How an ArrayList Grows Past Its Capacity

The capacity of an ArrayList is the length of its internal array, and the size is the number of slots in use. In JDK 25, new ArrayList<>() starts with an empty shared array and allocates 10 slots on the first add(). When all slots are used, the next add() creates an array about 1.5 times larger and copies the elements into it, so the capacity goes 10, 15, 22, 33 and so on.

Each copy costs O(n), but the copies happen less often as the list grows, so add() at the end takes amortized constant time. When we know the final size, we pass it to the constructor or call ensureCapacity() and skip the intermediate copies.

ArrayList<String> orders = new ArrayList<>(1_000);   // room for 1,000, size 0
orders.add("A-1");
int used = orders.size();                              // 1
orders.ensureCapacity(5_000);                          // one resize up front
orders.trimToSize();                                   // capacity = size

Removing elements lowers the size but never the capacity. A list that once held a million elements keeps its large internal array until we call trimToSize() or let the list be garbage collected.

3. Primitives, Boxing and Memory Use

An int[] stores the numbers themselves, while an ArrayList<Integer> stores references to separate Integer objects. On a typical 64-bit HotSpot JVM with compressed references, an int slot takes 4 bytes, whereas each Integer element takes a 4-byte reference plus a 16-byte object, unless the value comes from the cache of small integers. For a million numbers, that is about 4 MB against about 20 MB.

Boxing also changes how the values behave. The operator == compares the references of two Integer objects, and a null element throws an exception when Java unboxes it into an int.

List<Integer> scores = new ArrayList<>(List.of(1000, 1000, 7));
boolean sameObject = scores.get(0) == scores.get(1);        // false
boolean sameValue = scores.get(0).equals(scores.get(1));    // true
Integer removed = scores.remove(1);                         // 1000, removes index 1
boolean gone = scores.remove(Integer.valueOf(7));           // true, removes value 7
List<Integer> withNull = new ArrayList<>();
withNull.add(null);
int value = withNull.get(0);                                // NullPointerException

The overload remove(int index) wins over remove(Object o) for an int argument, which is a common bug with lists of numbers. The differences between the two kinds of arrays are covered in primitive and wrapper arrays.

4. Type Safety of Arrays and Generic Lists

Arrays are covariant, which means a String[] is also an Object[]. The compiler accepts writing an Integer into such a variable, and the JVM rejects it at runtime with an ArrayStoreException.

Object[] labels = new String[2];
labels[0] = "sale";
labels[1] = 42;                           // ArrayStoreException: java.lang.Integer

Generic lists are invariant, so a List<String> is not a List<Object>, and the same mistake never compiles. The price is that generics and arrays do not mix well, because Java cannot create an array of a generic type.

// does not compile: incompatible types, and generic array creation
List<Object> labels = new ArrayList<String>();
List<String>[] pages = new List<String>[3];

The compile error is the better outcome, because the bug shows up while we type and not in production. Inside our own generic classes we therefore keep elements in a List<T> instead of a T[].

5. Cost of Common Operations

Both structures sit on a plain array, so the costs are close. The difference is who writes the copying code. With an array, a new element at the end means resizing the array ourselves with Arrays.copyOf(), whereas ArrayList does the same copy internally and only when its capacity runs out.

OperationArrayArrayList
Read or write by indexO(1)O(1)
Add at the endO(n), new array every timeAmortized O(1)
Insert or remove in the middleO(n), manual System.arraycopy()O(n), add(i, e) and remove(i) shift elements
Search by valueO(n), loop or streamO(n), contains(), indexOf()
SortArrays.sort()list.sort(), which sorts the internal array

For data of primitives, the array is faster in practice, mostly because it avoids the boxed objects and keeps the numbers next to each other in memory. For lists of objects, an ArrayList and an array perform almost the same, so the choice comes down to the API. If a hot loop needs proof, we measure both with JMH instead of guessing.

6. Grids and Nested Lists

A two-dimensional array such as int[][] is an array of arrays, which suits data with a fixed shape like a seating plan. A list of lists models rows of different and changing lengths, such as the comments under each post.

int[][] seats = new int[3][4];            // 3 rows, 4 seats each
seats[1][2] = 1;                          // book row 1, seat 2
int rows = seats.length;                  // 3
List<List<String>> comments = new ArrayList<>();
comments.add(new ArrayList<>(List.of("nice")));
comments.add(new ArrayList<>());
comments.get(1).add("thanks");
String second = comments.get(1).get(0);   // "thanks"

7. Switching Between an Array and an ArrayList

Real code moves data across the boundary all the time, for example when a library method returns an array and our service works with lists. The methods Arrays.asList() and toArray() cover both directions for object arrays, and a stream handles primitive arrays.

String[] fruits = {"apple", "kiwi"};
List<String> view = Arrays.asList(fruits);                 // fixed-size view of the array
ArrayList<String> list = new ArrayList<>(Arrays.asList(fruits));   // [apple, kiwi]
boolean added = list.add("fig");                           // true
String[] back = list.toArray(String[]::new);               // [apple, kiwi, fig]
int[] nums = {3, 1};
List<Integer> boxed = Arrays.stream(nums).boxed().toList();   // [3, 1]

The list from Arrays.asList() writes through to the array and throws UnsupportedOperationException on add(), so we wrap it in new ArrayList<>() when the list must grow. All the variants, with their pitfalls, are in the guide to converting between a List and an array.

8. When to Use an Array and When an ArrayList

For most application code, ArrayList is the default, because the size of user data is rarely fixed and the List API works with streams, other collections and frameworks such as Spring and Jackson. Arrays are the better tool in a few specific situations.

SituationPickReason
Items added and removed at runtime, such as a cart or search resultsArrayListGrows on its own, rich API
Fixed number of slots, such as 7 days or 12 monthsArraySize never changes, simple syntax
Millions of numbers, image pixels, audio samplesint[], double[], byte[]No boxing, about 5 times less memory
Method parameters and return values in public APIsListCallers can pass any list, generics check types
APIs that require arrays, such as varargs and main(String[] args)ArrayRequired by the signature
Shared between threadsCopyOnWriteArrayList or a lockNeither type is thread-safe

For example, a fitness app that tracks steps per weekday stores them in an int[7], because a week always has seven days. The same app keeps the list of workouts a user logged today in an ArrayList, because that number changes with every new entry.

9. Array vs ArrayList FAQs

Performance, primitives and thread safety lead the questions developers ask about the two types.

9.1. Is an ArrayList slower than an array?

Only slightly for objects, and noticeably for primitives. Access by index has the same cost, but get() adds a bounds check and a method call that the JIT compiler mostly removes. The real gap comes from boxing, because an ArrayList<Integer> stores references to objects instead of plain numbers.

9.2. Can an ArrayList store int values?

Not as primitives. Type arguments must be reference types, so ArrayList<int> does not compile, and we use ArrayList<Integer>. Java boxes each int into an Integer on add() and unboxes it on get().

9.3. Why does an array use length and an ArrayList use size()?

The length of an array is a final field that the JVM sets at creation, because the length never changes. The size() of a list is a method, because the count changes with every add() and remove() and differs from the capacity of the internal array.

9.4. Is ArrayList thread-safe?

No. If several threads modify an ArrayList, we synchronize the ArrayList with Collections.synchronizedList() or use CopyOnWriteArrayList for lists that are read much more often than changed.

10. Conclusion

An array has a fixed length, holds primitives without boxing, and checks element types only at runtime. An ArrayList wraps an internal array, grows it by about 50 percent when it fills up, and gives us generics and a full List API.

We choose ArrayList for data that changes in size and for public method signatures, and we keep arrays for fixed-size data, large primitive data and APIs that require them. Converting between the two takes one line in either direction.

11. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.