Back to Blog
Java

Java ArrayList vs Array: Key Differences

java arraylist vs array: Compare Java arrays and ArrayList: syntax, type safety, performance, and when each fits. Practical guidance for choosing the right structure.

JavaArrayListArraysCollectionsPerformanceType Safety
Illustration comparing a fixed-size array and a dynamically growing ArrayList, with a lock on the array and a plus icon on the list.

When deciding between Java's built-in array and the ArrayList class, the choice affects type safety, memory allocation, and how you handle dynamic data. This article compares java arraylist vs array across syntax, performance, and typical use cases, so you can pick the right structure for your code.

Core Syntax Differences Between Array and ArrayList

An array is a language-level construct with fixed length. You declare it with square brackets and allocate it with new or an initializer:

int[] numbers = new int[5]; String[] names = {"Alice", "Bob"};

An ArrayList is a class in the java.util package. It grows and shrinks automatically, but you must specify the element type using generics:

List<String> nameList = new ArrayList<>(); nameList.add("Alice"); nameList.add("Bob");

Arrays support direct indexed access with array[index]. ArrayList provides methods like get(index) and set(index, value). The syntax is different, but both allow constant-time access by index.

Arrays are covariant: String[] is a subtype of Object[]. ArrayList<String> is not a subtype of ArrayList<Object>; generics are invariant. This difference becomes important when you pass collections to methods expecting a supertype.

Type Safety and Generics

Arrays enforce type checking at runtime. If you try to store a String in an Integer[], the JVM throws an ArrayStoreException. ArrayList enforces type safety at compile time through generics, but erases the type at runtime.

Object[] objects = new String[3]; objects[0] = 42; // ArrayStoreException at runtime List<Object> objectList = new ArrayList<String>(); // compile error

The compile-time check for ArrayList prevents many type mismatches before the code runs. However, because of type erasure, you cannot create an array of a generic type directly, such as new ArrayList<String>[10]. This limitation is why you often see List<List<String>> instead of an array of lists.

Performance and Memory Behavior

Arrays are a contiguous block of memory. Accessing an element requires only an index calculation, and there is no per-element object overhead. For primitive types like int, double, or boolean, an array stores the raw values directly. An ArrayList stores references to objects, so a primitive value must be boxed into a wrapper object like Integer or Double. This boxing adds memory overhead and a small CPU cost during read and write operations.

ArrayList also maintains a backing array that grows when needed. When the internal array is full, the class creates a larger array and copies the elements. This resizing is amortized O(1) per add, but a single add can be O(n) when a resize occurs. If you know the approximate size in advance, you can pass an initial capacity to the constructor to avoid repeated resizing.

List<Integer> list = new ArrayList<>(1000);

For primitive-heavy workloads, arrays are more memory-efficient and faster because they avoid boxing and indirection. For object references, the difference is smaller because both store references, though ArrayList still has the backing array and a small object header.

When to Use an Array

Use an array when the size is fixed and known at compile time or when you need to store primitives without boxing. Arrays are also useful when you need a simple, low-level data structure for a short-lived computation, such as a buffer or a fixed set of constants.

int[] buffer = new int[256]; String[] weekdays = {"Mon", "Tue", "Wed", "Thu", "Fri"};

Arrays integrate with varargs and native methods. They also provide a compact syntax for literal initialization. If you never need to add or remove elements, an array avoids the overhead of an ArrayList and makes the fixed size explicit in the code.

When to Use an ArrayList

Use ArrayList when the number of elements can change at runtime, or when you need to insert or remove elements at arbitrary positions. It provides useful methods like add, remove, contains, and indexOf that arrays lack. ArrayList also works with the Java Collections Framework, so you can pass it to methods that accept List, iterate with for-each, or use Collections utilities.

List<String> tasks = new ArrayList<>(); tasks.add("compile"); tasks.add("test"); tasks.remove("compile");

If you are building a collection by reading from a file, a network stream, or a user interface, you rarely know the final size in advance. ArrayList handles growth automatically, and the code remains readable without manual array resizing.

Converting Between Arrays and ArrayLists

You often need to switch between the two forms, especially when working with legacy APIs or libraries that expect one or the other.

To convert an array to an ArrayList, use Arrays.asList or create a new ArrayList from the array:

String[] arr = {"a", "b"}; List<String> list = new ArrayList<>(Arrays.asList(arr));

Note that Arrays.asList returns a fixed-size list backed by the array. You cannot add or remove elements through that view, but you can change existing elements. Wrapping it in a new ArrayList gives you a fully modifiable list.

To convert an ArrayList back to an array, use toArray:

List<String> list = new ArrayList<>(); list.add("x"); String[] arr = list.toArray(new String[0]);

The new String[0] argument is a type token. Java uses it to determine the runtime type of the returned array. Passing a zero-length array is a common idiom and works efficiently in modern JVMs.

Common Pitfalls and Edge Cases

One frequent mistake is using Arrays.asList and then calling add or remove, which throws an UnsupportedOperationException. Another is assuming that ArrayList is always better because it is more flexible, but for primitive-heavy, fixed-size data, an array is often the better choice.

When you create an array of a reference type, all elements are initially null. For an ArrayList, the list starts empty; you must add elements before accessing them. Accessing an index beyond the current size throws IndexOutOfBoundsException for both, but arrays also throw ArrayIndexOutOfBoundsException if you use a negative index or an index greater than or equal to length.

Generics and arrays do not mix well. You cannot create an array of a generic type directly, so code like new List<String>[5] fails to compile. If you need an array of lists, you must create a raw array and cast, which generates an unchecked warning. Prefer using List<List<String>> instead.

Another subtle issue is that ArrayList allows null elements, and arrays also allow null for reference types. If your logic treats null as a sentinel value, both structures behave the same. However, primitive arrays cannot store null, which is sometimes an advantage because it avoids accidental null dereferences.

For concurrent access, neither arrays nor ArrayList are thread-safe. If multiple threads modify an ArrayList, you must synchronize externally or use a thread-safe variant like CopyOnWriteArrayList. Arrays have no built-in synchronization either, but their fixed size makes them easier to share safely if you only read them after publication.

java arraylist vs array: Practical Usage and Code Examples | RYUSLOG DEV