Stream API ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-09-16

Convert int Array to List<Integer> and Back in Java

You have int[] numbers = {1, 2, 3}, you write Arrays.asList(numbers), and the result refuses to be a List<Integer>. Change the type to make it compile and list.size() prints 1 instead of 3 — the whole array became a single element. That one line is where most developers meet this topic for the first time.

Converting an array to a collection means moving the elements of a fixed-length array (int[], Integer[]) into a List<Integer> — and back again. An array stores its elements in a contiguous block of a fixed size, while List is an interface with convenient methods for adding, removing and filtering. A primitive array cannot go into a collection directly: Java collections store objects only, so each int has to be boxed into an Integer.

Below are the working conversions in both directions — with the Stream API, with a plain loop, plus the ArrayList, Integer[] and Arrays.asList() cases that trip people up.

int[] array to List<Integer>

A primitive array becomes a list in three steps: Arrays.stream() creates an IntStream, boxed() wraps every int into an Integer, and a terminal operation collects the result into a list.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

int[] numbers = {1, 2, 3, 4, 5};

List<Integer> list = Arrays.stream(numbers) // IntStream
                           .boxed()         // Stream<Integer>
                           .collect(Collectors.toList());

System.out.println(list); // [1, 2, 3, 4, 5]

Since Java 16 the stream has a shorter terminal method, toList():

List<Integer> list = Arrays.stream(numbers).boxed().toList();

Important

The list returned by Stream.toList() is unmodifiable: calling add() or set() on it throws UnsupportedOperationException. If you plan to modify the list afterwards, use collect(Collectors.toList()) or ask for an ArrayList explicitly.

Get an ArrayList<Integer>, not just a List

List is an interface, and the implementation behind it is not guaranteed by the specification. When your method signature requires an actual ArrayList<Integer>, ask for it:

import java.util.ArrayList;

// Option 1: collect straight into an ArrayList
ArrayList<Integer> arrayList = Arrays.stream(numbers)
        .boxed()
        .collect(Collectors.toCollection(ArrayList::new));

// Option 2: wrap an existing list
ArrayList<Integer> copy = new ArrayList<>(list);

arrayList.add(6); // works: the size is not fixed
System.out.println(arrayList); // [1, 2, 3, 4, 5, 6]

List<Integer> back to int[] array

The reverse conversion mirrors the first one: mapToInt() unboxes every Integer into an int, and toArray() builds the primitive array.

List<Integer> list = List.of(10, 20, 30, 40);

int[] array = list.stream()
                  .mapToInt(Integer::intValue)
                  .toArray();

System.out.println(Arrays.toString(array)); // [10, 20, 30, 40]

Exactly the same call works for an ArrayList<Integer>ArrayList is a List, so arrayList.stream().mapToInt(Integer::intValue).toArray() gives you the int[] too.

If the list can contain null, mapToInt(Integer::intValue) fails with a NullPointerException during unboxing. Filter first: .filter(Objects::nonNull).

Integer[], int[] and List<Integer> are three different types

int[] is an array of primitives, Integer[] is an array of wrapper objects, and List<Integer> is a collection of those wrappers. They are not assignable to each other — int[] a = integerArray; is a compile error — so every pair needs an explicit conversion.

// int[] -> Integer[]
int[] primitives = {1, 2, 3};
Integer[] boxed = Arrays.stream(primitives)
                        .boxed()
                        .toArray(Integer[]::new);

// Integer[] -> int[]
Integer[] source = {4, 5, 6};
int[] unboxed = Arrays.stream(source)
                      .mapToInt(Integer::intValue)
                      .toArray();

// Integer[] -> mutable List<Integer>
List<Integer> list = new ArrayList<>(Arrays.asList(source));

// List<Integer> -> Integer[]
Integer[] back = list.toArray(new Integer[0]);

Note that Arrays.asList(source) really does produce a three-element List<Integer> here, because Integer[] is an array of objects. The identical code behaves completely differently for int[].

Arrays.asList() with a primitive array: where it breaks

Arrays.asList() is declared with a varargs parameter T... a, and a type parameter T must be a reference type. An int[] is itself an object, so the compiler treats the whole array as one argument and infers List<int[]> with a size of 1.

int[] numbers = {1, 2, 3};

// Wrong: a list of ONE element - the array itself
List<int[]> wrong = Arrays.asList(numbers);
System.out.println(wrong.size()); // 1

// Right: unbox through a stream first
List<Integer> right = Arrays.stream(numbers).boxed().toList();
System.out.println(right.size()); // 3

One more quirk

Even for object arrays such as Integer[] or String[], Arrays.asList() returns a fixed-size list backed by the original array: add() and remove() throw UnsupportedOperationException, and set() writes through to the array. For a fully mutable list, wrap it: new ArrayList<>(Arrays.asList(source)).

Without the Stream API: a plain loop

A loop is still a perfectly good tool. It reads clearly in teaching code, allocates no intermediate stream objects, and stays the only option when you need index arithmetic or an early break in the middle of the conversion.

int[] arr = {1, 2, 3};

// Array -> List
List<Integer> list = new ArrayList<>();
for (int value : arr) {          // iterate the array with for-each
    list.add(value);             // autoboxing: int -> Integer
}

// List -> array
int[] backToArray = new int[list.size()];
for (int i = 0; i < list.size(); i++) {  // iterate by index
    backToArray[i] = list.get(i);        // auto-unboxing: Integer -> int
}

Once the data is in a list, you can walk it with the same for-each or with forEach():

for (int value : arr) {
    System.out.println(value);
}

list.forEach(System.out::println);

The same pattern works for any Collection

Nothing in the recipe is specific to List. Swap the collector and you get a Set, a LinkedList or any other Collection — and stream().mapToInt().toArray() converts every one of them back to int[].

import java.util.LinkedList;
import java.util.Set;

int[] numbers = {3, 1, 2, 3};

// int[] -> Set<Integer> (duplicates are dropped)
Set<Integer> set = Arrays.stream(numbers).boxed().collect(Collectors.toSet());

// int[] -> LinkedList<Integer>
LinkedList<Integer> linked = Arrays.stream(numbers)
        .boxed()
        .collect(Collectors.toCollection(LinkedList::new));

// any Collection<Integer> -> int[]
int[] fromSet = set.stream().mapToInt(Integer::intValue).toArray();

For an array of objects the job is even shorter: List<String> words = new ArrayList<>(Arrays.asList(stringArray)); — no boxing step is needed, because the elements are already objects.

Why List<int> does not compile

Java generics accept reference types only. At runtime the type parameter is erased to Object, and a primitive int is not an object, so List<int> is rejected by the compiler and the wrapper type List<Integer> is used instead. boxed() and mapToInt() are precisely the bridges between those two worlds.

Cheat sheet: which conversion to use

Conversion Code Notes
int[] → List<Integer> Arrays.stream(arr).boxed().collect(Collectors.toList()) Mutable list; .toList() (Java 16+) returns an unmodifiable one
List<Integer> → int[] list.stream().mapToInt(Integer::intValue).toArray() A null element causes an NPE
int[] → ArrayList<Integer> ...boxed().collect(Collectors.toCollection(ArrayList::new)) When you need the concrete implementation
int[] → Integer[] Arrays.stream(arr).boxed().toArray(Integer[]::new) An array of wrappers, not a list
Integer[] → List<Integer> new ArrayList<>(Arrays.asList(arr)) Without the wrapper the list has a fixed size
List<Integer> → Integer[] list.toArray(new Integer[0]) Passing a zero-length array is the idiomatic form

The Stream API wins on readability — one line, no temporary variables. A loop wins when you need full control over the traversal. The one thing to remember either way: Arrays.asList() does not convert an array of primitives.

Frequently asked questions

Why does Arrays.asList(intArray) return a list with one element?

The method is declared as Arrays.asList(T... a), and the type parameter T can only be a reference type. An int[] is an object itself, so it is passed as a single argument and the result is a List<int[]> of size 1. To get a List<Integer>, use Arrays.stream(arr).boxed().

How do I convert an int array to an ArrayList specifically?

Use Arrays.stream(arr).boxed().collect(Collectors.toCollection(ArrayList::new)), or wrap an existing collection with new ArrayList<>(list). Both give you a mutable ArrayList<Integer> that accepts add() and remove().

What is the difference between Stream.toList() and collect(Collectors.toList())?

toList() was added in Java 16 and always returns an unmodifiable list that permits null elements. Collectors.toList() returns a mutable list, in practice an ArrayList, but the specification does not guarantee the exact implementation.

How do I convert Integer[] to int[] and back?

From Integer[] to int[]: Arrays.stream(boxed).mapToInt(Integer::intValue).toArray(). The other way round: Arrays.stream(primitives).boxed().toArray(Integer[]::new). A plain assignment between the two array types does not compile, because they are unrelated types.

Which is faster, a plain loop or the Stream API?

On small arrays the loop is usually faster: it allocates no stream objects and needs no JIT warm-up. In practice the gap only matters in hot code paths with millions of elements, so choose by readability and, if performance is a real requirement, measure it with a benchmark such as JMH.

Comments

Please log in or register to have a possibility to add comment.