Back to Blog
Java

Java Array vs ArrayList: Choosing the Right Structure

java array vs arraylist: Compare Java arrays and ArrayLists on syntax, resizing, generics, performance, and memory. Learn which structure fits your use case.

Java arraysArrayListJava collectionsJava performanceJava data structures
A visual comparison between a fixed-size array and a dynamically resizing ArrayList in Java

java array vs arraylist requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When working with ordered data in Java, the choice between an array and an ArrayList often comes down to whether the size is known in advance and whether you need the convenience of the Collections framework. Both store elements sequentially, but they differ in syntax, type safety, memory behavior, and runtime cost. This article examines the practical differences so you can decide which structure to use in a given situation.

Core Syntax Differences

An array is a built-in language construct. You declare it with a type and a size, or with an initializer list:

String[] names = new String[3]; names[0] = "Ada"; int[] numbers = {1, 2, 3};

An ArrayList is a class from java.util. It holds objects, not primitives directly, and uses generics to enforce a compile-time element type:

ArrayList<String> names = new ArrayList<>(); names.add("Ada"); names.add("Grace");

Because ArrayList is part of the Collections framework, it provides methods like add, remove, contains, and size. Arrays have no methods; you must track the length manually via the length field.

Fixed Size vs Dynamic Resizing

An array's length is fixed at creation. You cannot add or remove elements without creating a new array and copying the contents. This is often the first thing developers notice when moving from arrays to ArrayLists.

ArrayList grows automatically when you add more elements than its current capacity. Internally it uses an array, and when that array is full, it allocates a new, larger array (typically 1.5 times the old size) and copies the elements over. This resizing is invisible to the caller but has a runtime cost.

If you know the approximate number of elements in advance, you can pass an initial capacity to the ArrayList constructor to reduce the number of resizes:

ArrayList<String> names = new ArrayList<>(100);

This does not cap the size; it only sets the starting internal array length.

Type Safety and Generics

Arrays are covariant and reified. Covariance means String[] is a subtype of Object[]. Reification means the runtime knows the component type. This leads to a well-known pitfall:

Object[] objects = new String[10]; objects[0] = 42; // ArrayStoreException at runtime

The runtime checks the actual component type and throws an exception if you try to store an incompatible element.

ArrayList uses generics, which are erased at runtime. The compiler enforces type safety, but the runtime sees only ArrayList, not ArrayList<String>. This means you cannot create an array of a generic type directly, such as new ArrayList<String>[10]. You can, however, create an array of ArrayList and cast it, but that generates an unchecked warning and is generally discouraged.

For most code, generics give stronger compile-time guarantees than arrays because they prevent you from inserting a wrong type in the first place, rather than throwing an exception later.

Primitive Types and Boxing

Arrays can store primitives directly, such as int, double, or boolean. This is memory-efficient and avoids boxing overhead.

ArrayList cannot store primitives directly. It only works with reference types, so you must use wrapper classes like Integer, Double, or Boolean. When you add a primitive to an ArrayList, it is automatically boxed into the wrapper object. When you retrieve it, it is unboxed back to the primitive.

This boxing and unboxing has a runtime cost and increases memory usage. For large collections of primitive values, an array of primitives is significantly more efficient than an ArrayList of wrappers. If you need dynamic resizing with primitives, consider using a specialized collection like IntArrayList from third-party libraries, but that adds a dependency.

Performance and Memory Behavior

Performance between arrays and ArrayLists is not a simple "array is faster" statement. Accessing an element by index is O(1) for both. The array access is a direct memory read; the ArrayList access goes through a method call that reads the internal array, but the difference is negligible in most applications.

The main performance differences come from resizing and boxing.

  • Resizing: Adding to an ArrayList that has spare capacity is O(1). When the internal array is full, resizing is O(n) because it copies all elements. Over a sequence of additions, the amortized cost is still O(1) per add, but occasional spikes occur. Arrays never resize, so you must plan the size upfront or pay the cost of copying manually.
  • Boxing: Using ArrayList with primitives incurs allocation and garbage collection overhead for each wrapper object. This can be significant in tight loops or large data sets.

Memory usage also differs. An array of int uses 4 bytes per element. An ArrayList of Integer uses a reference (typically 4 or 8 bytes) plus the Integer object itself (16 bytes or more), plus the internal array's overhead. For large collections, this can be several times more memory.

If you need to store many primitive values and the size is known or can be bounded, an array is the better choice. If you need dynamic resizing and the collection size is modest, the convenience of ArrayList often outweighs the memory overhead.

Common Operations and Convenience

ArrayList provides a rich API that arrays lack. For example, to remove an element from an array, you must shift subsequent elements manually. With ArrayList, remove(int index) handles that for you. Similarly, contains performs a linear search, which is convenient but O(n).

ArrayList also integrates with the Collections framework, so you can use Collections.sort, Collections.reverse, or stream operations directly. Arrays have utility methods in java.util.Arrays, such as Arrays.sort, Arrays.asList, and Arrays.copyOf, but they are not as comprehensive.

A common pattern is to convert an array to a list for easier manipulation:

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

Note that Arrays.asList returns a fixed-size list backed by the array. You cannot add or remove elements, but you can set existing elements. If you need a fully resizable list, pass it to a new ArrayList:

ArrayList<String> resizable = new ArrayList<>(Arrays.asList(array));

When to Use Array vs ArrayList

Use an array when:

  • The size is fixed and known at creation.
  • You are storing primitives and want to avoid boxing overhead.
  • You need the simplest possible structure with no method call overhead.
  • You are working with low-level APIs that require arrays, such as String.toCharArray() or File.readAllBytes().

Use an ArrayList when:

  • The size changes dynamically.
  • You need to insert or remove elements in the middle.
  • You want to use the Collections API, such as sorting, searching, or stream operations.
  • You are storing reference types and the collection size is not known in advance.

There is no universal rule; the decision depends on your specific requirements. In many modern Java applications, ArrayList is the default choice for collections because of its flexibility, even with the small overhead. Arrays remain essential for performance-critical code and for interoperability with APIs that expect them.

A Practical Example: Building a Dynamic List

Consider a method that reads integers from a file and returns them. The number of integers is unknown until the file is read. Using an array would require two passes or a temporary resizable structure. ArrayList makes this straightforward:

ArrayList<Integer> values = new ArrayList<>(); try (Scanner scanner = new Scanner(Path.of("data.txt"))) { while (scanner.hasNextInt()) { values.add(scanner.nextInt()); } } // Later, convert to array if needed Integer[] array = values.toArray(new Integer[0]);

If you later need an array of primitives, you can copy the elements manually, but the ArrayList approach avoids manual resizing logic.

Compatibility and API Boundaries

When your code interacts with external libraries, the required type often dictates the choice. Many Java APIs accept or return arrays, especially older ones. For example, String.split returns a String[], and ByteArrayOutputStream.toByteArray() returns a byte[]. If you need to pass data to such a method, an array is necessary.

Conversely, the Collections framework is the standard for modern Java. If you are building a public API, exposing an ArrayList or List is generally more flexible because it allows the caller to choose the implementation and allows future changes without breaking callers. Returning an array commits you to a fixed-size structure.

A common compromise is to use an ArrayList internally for dynamic growth and then convert to an array when returning a fixed snapshot. This gives you both convenience and a stable interface.

The Impact of Java Versions

Java 8 introduced streams, which work with both arrays and collections. You can stream over an array using Arrays.stream(array) and over an ArrayList using list.stream(). The performance characteristics are similar, but streams add overhead and are best used for complex processing rather than simple iteration.

Java 9 added List.of() which returns an immutable list. This is not an ArrayList, but it is another alternative when you need a fixed-size, read-only collection. It is more memory-efficient than an ArrayList because it does not allocate extra capacity.

None of these features change the fundamental tradeoff between arrays and ArrayLists. The decision still rests on whether you need dynamic sizing and collection methods, or whether fixed size and primitive storage are more important.

Memory and Garbage Collection Considerations

ArrayList holds references to objects. When you remove an element, the reference is set to null, allowing the object to be garbage collected if no other references exist. Arrays also hold references, but if you remove an element by shifting, you must manually null the last slot to avoid a memory leak in long-lived arrays.

For primitive arrays, there is no such issue because the values are stored directly. For ArrayList of wrappers, each wrapper is a separate object, so garbage collection pressure increases with the number of elements. If you create and discard many ArrayLists in a loop, the wrapper objects add allocation overhead.

In performance-sensitive code, prefer primitive arrays when the element type is primitive and the size is stable. Use ArrayList when the convenience of dynamic resizing outweighs the cost of boxing.

A Note on Arrays.asList and Fixed-Size Lists

A common mistake is to treat Arrays.asList as a fully resizable list. It returns a java.util.Arrays$ArrayList that is fixed-size. Calling add or remove throws UnsupportedOperationException. This is a frequent source of runtime errors. If you need a resizable list, wrap it in a new ArrayList as shown earlier.

Similarly, List.of returns an immutable list, so any modification attempt fails. Understanding these differences prevents surprises when switching between arrays and collections.

Final Recommendation by Scenario

The choice between array and ArrayList is not about which is "better" overall. It depends on the context.

  • For a fixed set of elements that will not change, and especially for primitives, use an array.
  • For a collection that grows or shrinks dynamically, use ArrayList.
  • If you need to pass data to an API that expects an array, you may need to convert at the boundary.
  • If you want to avoid boxing overhead but need dynamic sizing, consider a third-party primitive collection or design your own resizable array.

Understanding the underlying mechanics—fixed vs resizable, primitive vs object, compile-time vs runtime type checking—lets you make an informed decision rather than defaulting to one structure out of habit.

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