Arrays in Java are fixed-size array data structure implementation, provide run-time type safety

ArrayLists are resizable array implementation, a part of the Collections framework, offer compile-time type safety - through generics

Let's walk through this tutorial to see the differences between Array and ArrayList in Java in detail examples

Fixed vs dynamic size implementation

  • An array is an object container that holds a fixed number of single type elements. Once an array object is created, its length/capacity never changes, you can't add more elements than the initial capacity

    The following example declares an array of length 3 and assigns value to the allocated item. There're no ways for adding an additional item as the capacity's fixed

int[] arr = new int[3];  
arr[0] = 1;  
arr[1] = 2;  
arr[2] = 3;
  • ArrayList, backed by an array, auto grow its capacity internally, you can add more elements than the initial

    The below example declares an ArrayList of length 3 and initializes its items. You can add the fourth item, although the initial capacity is 3

List<Integer> lst = new ArrayList<>(3);  

Type safety

Arrays and generics have a different type of rules. Generics offer compile-time while arrays offer runtime type safety. Generics are implemented in the Java Collection framework to specify the type of objects stored in a collection instance

Covariance vs Invariance

  • Arrays are covariant. This means if S is a subtype of T, then S[] is also a subtype of T[]. The below code is valid
Object[] arr = new String[3];
  • Generics, on the opposite, are invariance. This means if S is a subtype of T, then List<S> isn't a subtype of List<T>. The following code is invalid
List<Object> lst = new ArrayList<String>();  

Reification vs Erasure

  • Arrays are reified, the data type is enforced at runtime. The below code is success at compile-time but failed at runtime
Object[] arr = new String[3];  
arr[0] = 1 // throws ArrayStoreException at runtime  
  • Generics are erasure, the data type is enforced at compile-time and erased at runtime. The below is failed at compile time
List<Object> lst = new ArrayList<String>(); // Incompatible types  

Primitive vs reference type

  • ArrayLists can hold only reference type elements. The following code can't compile as the type argument int is a primitive type
List<int> lst = new ArrayList<>();
  • Java internally does auto-boxing primitive values when added into an ArrayList of the corresponding wrapper classes.

    Consider the following codes, the latter version is converted from the former by the compiler at runtime

List<Integer> lst = new ArrayList<>();  
List<Integer> lst = new ArrayList<>();  
  • Arrays can hold both primitive and reference type elements
int[] arr1 = {1, 2, 3};  
Object[] arr2 = new int[3];


  • You can query the capacity of an array but can't on ArrayList, although you can query the number of elements in the list
int[] arr = {1, 2, 3};  

List<Integer> lst = new ArrayList<>(List.of(1, 2, 3));  
  • ArrayLists provide more convenient operations than arrays such as iterating and removing
List<Integer> lst = new ArrayList<>(List.of(1, 2, 3));  
lst.forEach(ele -> System.out.println(ele));  
lst.removeIf(ele -> ele > 1);  


  • Arrays are more simply for typing, especially when working with multi-dimensional
// initialize
int[][] arr = new int[10][10];  
for (int i=0; i<10; i++) {  
    for (int j=0; j<10; j++) {
        arr[i][j] = 1;

// update value of element at [0,0]
arr[0][0] = 10;  
  • On the other hand, ArrayLists are more verbose
// initialize
List<List<Integer>> lst = new ArrayList<>();  
for (int i=0; i<10; i++) {  
    List<Integer> tmp = new ArrayList<>();
    for (int j=0; j<10; j++) {

// update value of element at [0,0]
lst.get(0).set(0, 10);


In this tutorial, we learned the differences between arrays and ArrayLists in detail examples. ArrayList is usually preferred to Array thanks to its type-safety enforcement at compile time offering through generics