Java Arrays
An array in Java is a fixed-size, ordered collection of elements that all share the same type and live together in a single block of memory. Arrays let you group related values — a list of scores, a grid of pixels, a batch of sensor readings — under one variable name, and access any element instantly by its numeric index. They are the simplest and fastest data structure Java offers, and they form the foundation that more flexible collections like ArrayList are built on top of. This lesson covers arrays from first principles all the way through memory internals, multidimensional arrays, the Arrays utility class, and the mistakes that trip up almost every new Java programmer.
Overview: How Arrays Work
Every array in Java has three defining properties: a fixed length chosen when the array is created, a single element type shared by every slot (int, String, boolean, another array, etc.), and zero-based indexing, meaning the first element is at index 0 and the last is at index length - 1. Once an array is created, its length can never change — there is no way to add or remove a slot. If you need more room, you must create a brand-new, larger array and copy the existing elements into it (the Arrays.copyOf method, shown below, does exactly this).
Arrays Are Objects
Even an array of primitives such as int[] is an object in Java, allocated on the heap, not the stack. The variable you declare (for example int[] numbers) is a reference to that heap object, exactly like a reference to any other object. This has real consequences:
- Arrays expose their size through a public
lengthfield, not a method — there are no parentheses after it, unlikeString.length(). - Assigning one array variable to another copies the reference, not the data. Both variables end up pointing at the same underlying array, so changes made through one are visible through the other.
- When you pass an array to a method, the method receives a copy of the reference, but that reference points at the same array, so modifying elements inside the method is visible to the caller after the method returns.
- Because arrays are objects, an array reference can also be
null, meaning it doesn’t point at an array at all — reading.lengthor an element from anullarray throws aNullPointerException.
Default Values
When you create an array with new, Java automatically fills every slot with a default value based on the element type — you never get leftover “garbage” memory. Numeric types default to 0 (or 0.0 for floating-point types), boolean defaults to false, char defaults to the null character, and any reference type (String, custom objects, other arrays) defaults to null. This differs from local variables, which the compiler forces you to initialize before use.
Syntax
An array declaration has two parts: the element type followed by square brackets, and a name. You can declare, instantiate, and initialize an array separately or all at once.
| Form | Example | Meaning |
|---|---|---|
| Declaration | int[] arr; |
Declares a reference variable; no array exists yet (value is null). |
| Instantiation | arr = new int[5]; |
Creates an array of 5 ints, all initialized to 0, and assigns it to arr. |
| Combined | int[] arr = new int[5]; |
Declares and instantiates in one statement. |
| Array literal | int[] arr = {1, 2, 3}; |
Declares and fills with known values; length is inferred (3). |
| Explicit literal | int[] arr = new int[]{1, 2, 3}; |
Same as above, but usable outside a declaration (e.g. as a method argument). |
The square brackets can also legally appear after the variable name (int arr[]), a holdover from C-style syntax — this compiles, but placing the brackets after the type (int[] arr) is the idiomatic and recommended style in Java.
int[] arrayName;
arrayName = new int[5];
int[] arrayName2 = new int[5];
int[] arrayName3 = {1, 2, 3, 4, 5};
int[] arrayName4 = new int[]{1, 2, 3};
System.out.println("arrayName2 length: " + arrayName2.length);
System.out.println("arrayName3 length: " + arrayName3.length);
System.out.println("arrayName4 length: " + arrayName4.length);
Output:
arrayName2 length: 5
arrayName3 length: 5
arrayName4 length: 3
Examples
Example 1: Declaring, Filling, and Reading a 1D Array
public class Main {
public static void main(String[] args) {
int[] scores = {85, 92, 78, 90, 65};
int sum = 0;
for (int i = 0; i < scores.length; i++) {
System.out.println("Score " + i + ": " + scores[i]);
sum += scores[i];
}
double average = (double) sum / scores.length;
System.out.println("Average: " + average);
System.out.print("All scores:");
for (int score : scores) {
System.out.print(" " + score);
}
System.out.println();
}
}
Output:
Score 0: 85
Score 1: 92
Score 2: 78
Score 3: 90
Score 4: 65
Average: 82.0
All scores: 85 92 78 90 65
The first loop uses an indexed for loop because it needs both the index i and the value scores[i]. The second loop uses the enhanced for loop (for (int score : scores)), which reads more cleanly when you only need each value, not its position. Notice the cast (double) sum — without it, sum / scores.length would perform integer division and truncate the result.
Example 2: Two-Dimensional Arrays
public class Main {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int total = 0;
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
System.out.print(matrix[row][col] + " ");
total += matrix[row][col];
}
System.out.println();
}
System.out.println("Sum of all elements: " + total);
}
}
Output:
1 2 3
4 5 6
7 8 9
Sum of all elements: 45
Java doesn’t have “true” multidimensional arrays the way some languages do — int[][] matrix is really an array of arrays: matrix is an array of three int[] references, and each of those references points to its own independent int[3]. That’s why the inner loop bound is written as matrix[row].length rather than a fixed number — each row could, in principle, have a different length (a “jagged” array), even though in this example all three rows happen to have three columns.
Example 3: The Arrays Utility Class
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] numbers = {42, 17, 8, 99, 23};
System.out.println("Original: " + Arrays.toString(numbers));
Arrays.sort(numbers);
System.out.println("Sorted: " + Arrays.toString(numbers));
int index = Arrays.binarySearch(numbers, 42);
System.out.println("Index of 42: " + index);
int[] expanded = Arrays.copyOf(numbers, 7);
System.out.println("Expanded: " + Arrays.toString(expanded));
int[] filled = new int[5];
Arrays.fill(filled, 9);
System.out.println("Filled: " + Arrays.toString(filled));
}
}
Output:
Original: [42, 17, 8, 99, 23]
Sorted: [8, 17, 23, 42, 99]
Index of 42: 3
Expanded: [8, 17, 23, 42, 99, 0, 0]
Filled: [9, 9, 9, 9, 9]
Printing an array directly with System.out.println(numbers) does not print its contents — it prints something like [I@1b6d3586, a type code plus a hash. Always use Arrays.toString() (or Arrays.deepToString() for nested arrays) to print readable contents. This example also shows why sorting matters: Arrays.binarySearch only works correctly on a sorted array — running it on an unsorted array gives an undefined result. Arrays.copyOf is the standard way to “grow” an array: it allocates a new array of the requested length, copies over the old elements, and pads any new slots with the default value (0 here).
Under the Hood: What the JVM Does
Conceptually, an array is stored as one contiguous block of memory on the heap, preceded by a small header that records its type and length. Reading arr.length is effectively free — the JVM does not count the elements each time, it just returns the stored value. Accessing arr[i] is (conceptually) computed as base_address + i * element_size, which is why array access is O(1) regardless of length or index.
Walking through int[] arr = new int[5]; followed by arr[2] = 10;:
- The JVM allocates a contiguous block on the heap large enough for five
intvalues, plus a header storing the type (int[]) and length (5). - Every slot is initialized to the default value for
int, which is0. - The reference to that heap block is stored in the local variable
arr. - For
arr[2] = 10;, the JVM checks that2is within the valid range[0, 5), then writes10at the corresponding offset. - If the index were out of range — say
arr[10]— the JVM refuses to touch memory outside the block and throwsArrayIndexOutOfBoundsExceptioninstead. This bounds check runs on every array access, which is what makes Java memory-safe compared to languages like C that let you read and write past the end of an array.
One more subtlety applies to arrays of objects. Because of a feature called array covariance, a String[] can be assigned to an Object[] variable, since every String is an Object. But the array still remembers its real, original component type at runtime. If you then try to store something other than a String through that Object[] reference, the JVM detects the mismatch at runtime and throws ArrayStoreException, even though the code compiled without complaint. This is a rare gotcha but worth knowing if you ever see that exception in a stack trace.
Common Mistakes
Mistake 1: Off-by-One Errors (ArrayIndexOutOfBoundsException)
The most common array bug is looping one step too far by using <= instead of < against .length. Valid indices run from 0 to length - 1, so arr[arr.length] is always one past the end.
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
}
}
Output:
1
2
3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Main.main(Main.java:5)
The fix is simply to use < instead of <=:
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
Output:
1
2
3
Mistake 2: Calling .length() Instead of Using .length
Arrays expose their size as a field called length, with no parentheses. This is easy to mix up with String.length(), which is a method. Writing arr.length() on an array is a compile-time error, not a runtime one:
int[] arr = {1, 2, 3};
System.out.println(arr.length()); // compile error: cannot find symbol -- length() is not a method on arrays
The fix is to drop the parentheses:
int[] arr = {1, 2, 3};
System.out.println("Length: " + arr.length);
Output:
Length: 3
A related mistake is assuming an array can grow, e.g. expecting arr[arr.length] = x; to “append” a value. Arrays never resize themselves — use Arrays.copyOf to build a larger array (as shown in Example 3), or switch to a java.util.ArrayList if the collection’s size needs to change at runtime.
Best Practices
- Use the enhanced
forloop (for (T x : array)) when you only need values, and the indexed loop when you need the position too. - Always bound loops with
array.lengthrather than a hardcoded number, so the code keeps working if the array’s size changes. - Use
java.util.Arrayshelper methods —toString,sort,fill,equals,copyOf,binarySearch— instead of hand-rolling loops for common operations. - Validate indices (especially ones derived from user input) before using them, rather than catching
ArrayIndexOutOfBoundsExceptionafter the fact. - Prefer array-literal syntax (
{1, 2, 3}) when the initial values are known ahead of time — it’s shorter and clearer than assigning each slot individually. - Reach for
ArrayListor anotherjava.utilcollection when the number of elements will change at runtime; arrays are the right tool only when the size is fixed and known. - Avoid returning a class’s internal array directly from a getter — return a copy (e.g. via
Arrays.copyOf) so callers can’t mutate your object’s internal state.
Practice Exercises
- Write a program that fills an
intarray of length 10 with the first ten even numbers (2, 4, 6, …, 20) and prints their sum. - Write a program that uses
Scannerto read five integers from the user into an array, then prints the largest and smallest values found. - Write a program that builds a 3×3
intmatrix where element[row][col]equalsrow * 3 + col, prints it, then prints its transpose (rows and columns swapped).
Summary
- An array is a fixed-size, zero-indexed collection of elements of the same type, allocated as a single object on the heap.
- Once created, an array’s length cannot change — use
Arrays.copyOfor anArrayListwhen you need dynamic sizing. lengthis a field (no parentheses), not a method likeString.length().- Multidimensional arrays in Java are arrays of arrays, which is why rows can have different lengths (jagged arrays).
- Every array access is bounds-checked at runtime, throwing
ArrayIndexOutOfBoundsExceptionon invalid indices — this is what keeps Java memory-safe. - The
java.util.Arraysclass provides ready-made methods for printing, sorting, searching, filling, and copying arrays — prefer them over manual loops.
