Back to Blog
Java

Java Arrays.deepToString: Print Nested Arrays

Learn how to use Java's Arrays.deepToString to print nested and multi-dimensional arrays with readable output, and see how it differs from Arrays.toString.

JavaArraysdeepToStringtoStringDebugging
Illustration of Java Arrays.deepToString converting a nested array into a readable string representation.

When you print a nested array with Arrays.toString, you often get output like [[I@1b6d3586, [I@7f31245a] instead of the actual elements. That happens because Arrays.toString does not recursively inspect an array's elements: each nested array is treated as an object, and its default Object.toString returns the class name and hash code. java.util.Arrays.deepToString solves this by recursively converting nested arrays into a readable string. This article explains how deepToString works, when to use it, and what to watch out for.

What Arrays.deepToString Does

The deepToString method is a static utility in java.util.Arrays. It accepts an Object[] and returns a string representing the array's contents, including nested arrays. Unlike Arrays.toString, which does not recurse into nested arrays, deepToString visits every dimension and produces a format similar to what you would write manually: [[1, 2], [3, 4]].

Here is a minimal example:

import java.util.Arrays; public class DeepToStringExample { public static void main(String[] args) { int[][] matrix = {{1, 2}, {3, 4}}; System.out.println(Arrays.deepToString(matrix)); } }

The output is [[1, 2], [3, 4]]. Null elements are handled gracefully: if an element is null, the output contains null instead of throwing an exception.

Since the method signature is deepToString(Object[] a), it works for int[][] and String[][], but not directly for a one-dimensional primitive array. For int[], use the corresponding Arrays.toString overload.

deepToString vs toString for Nested Arrays

The difference becomes clear when you compare the two methods on the same nested array.

int[][] grid = {{5, 6}, {7, 8}}; System.out.println(Arrays.toString(grid)); System.out.println(Arrays.deepToString(grid));

Arrays.toString(grid) calls toString on each row, and each row is an int[] object. Since int[] does not override toString, you get something like [[I@2a84a0, [I@3b07d329]. deepToString inspects each row and prints its elements, producing [[5, 6], [7, 8]].

The same principle applies to any array whose elements are themselves arrays, such as String[][], Object[], or arrays of custom objects. For a one-dimensional object array whose elements are not themselves arrays, Arrays.toString and deepToString produce the same output.

Using deepToString with Multi-Dimensional Arrays

Multi-dimensional arrays in Java are arrays of arrays. deepToString works for any depth, including 3D arrays.

int[][][] cube = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; System.out.println(Arrays.deepToString(cube));

Output: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]].

The method also handles ragged arrays, where inner arrays have different lengths, without issue. If the array is declared as Object[], it can contain a mix of primitive arrays, object arrays, and ordinary objects.

Handling Arrays of Objects and Custom Types

When an array contains objects that are not themselves arrays, deepToString uses each object's toString method to produce the string. For example:

class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return "(" + x + "," + y + ")"; } } Point[] points = {new Point(1, 2), new Point(3, 4)}; System.out.println(Arrays.deepToString(points));

Output: [(1,2), (3,4)]. If the object's class does not override toString, you will get the default ClassName@hashcode representation, so it is often worth implementing toString for domain objects that you plan to debug this way.

Performance and Memory Considerations

deepToString recursively traverses the array structure. For a large multi-dimensional array, this means the method visits every element, so the time complexity is O(n), where n is the total number of elements. The recursion depth is equal to the array's nesting depth. For typical 2D or 3D arrays, that is trivial, but for extremely deep structures you could hit a StackOverflowError. In practice, such depths are rare, but if you are building a generic debug utility that might receive arbitrary input, be aware of this limitation.

Memory usage is also proportional to the output string length. If you are printing a very large array, the resulting string can consume significant memory. For logging purposes, consider truncating the output or using a streaming approach if the array is huge.

Common Pitfalls and Edge Cases

deepToString detects direct or indirect self-references in arrays and prints [...] as the self-reference placeholder instead of recursing infinitely. For example, an array that contains itself is rendered with [...] rather than causing a stack overflow.

One pitfall is using deepToString on an array of a custom type that does not override toString; you will get unhelpful output. Another is assuming that deepToString works on any Iterable or collection; it does not. It only works on arrays. For List or Set, use the collection's own toString or a custom formatter. Collection toString implementations do not recursively unwrap arrays contained in the collection.

Also note that deepToString does not modify the array; it only creates a string representation. If you need to serialize an array to a format for persistence, this method is not suitable because it loses type information and is not reversible.

When to Use deepToString vs Manual Formatting

Use deepToString when you need a quick, readable representation of a nested array for debugging, logging, or assertion messages. It is concise and avoids writing custom loops.

Manual formatting is better when you need control over the output format, such as adding separators or prefixes, or when you need to handle large arrays with a streaming approach. For example, if you are building a CSV export from a 2D array, a loop gives you the flexibility to quote values and handle escaping.

In short, deepToString is the right tool for quick inspection, while manual formatting is appropriate when the output must meet a specific format or when performance constraints require you to avoid building a giant string in memory.

Java Arrays.deepToString: Practical Usage and Code Examples | RYUSLOG DEV