Of course! In Java, a "list in a list" is typically implemented using a List of List objects. The most common and flexible way to do this is with a List<List<E>>, where E is the type of elements you want to store in the inner lists.

Here’s a comprehensive guide covering:
- How to Declare and Initialize
- How to Add Elements
- How to Access Elements
- Common Operations (Iteration, Removal, etc.)
- Practical Example: A Student-Grade System
- When to Use This Structure
- Important Considerations
How to Declare and Initialize
You declare it just like any other generic type. For initialization, you should almost always use the ArrayList implementation, which is a good default choice.
import java.util.ArrayList; import java.util.List; // Declaration List<List<String>> listOfLists; // Initialization listOfLists = new ArrayList<>(); // This creates a list that will hold other lists.
Best Practice: Initialize Inner Lists on the Fly
When you add a new inner list to your main list, you must create it first.
// Create the outer list List<List<Integer>> matrix = new ArrayList<>(); // Create the first inner list and add it to the outer list List<Integer> row1 = new ArrayList<>(); row1.add(1); row1.add(2); row1.add(3); matrix.add(row1); // Create the second inner list and add it List<Integer> row2 = new ArrayList<>(); row2.add(4); row2.add(5); row2.add(6); matrix.add(row2); // Now, matrix looks like this: [[1, 2, 3], [4, 5, 6]]
How to Add Elements
You have two main operations:

- Adding a whole new list to the outer list.
- Adding an element to one of the inner lists.
List<String> fruits = new ArrayList<>();
List<String> vegetables = new ArrayList<>();
// Add elements to the inner lists
fruits.add("Apple");
fruits.add("Banana");
vegetables.add("Carrot");
vegetables.add("Broccoli");
// Create the outer list and add the inner lists to it
List<List<String>> groceryListByCategory = new ArrayList<>();
groceryListByCategory.add(fruits); // Adds the "fruits" list
groceryListByCategory.add(vegetables); // Adds the "vegetables" list
// groceryListByCategory is now: [["Apple", "Banana"], ["Carrot", "Broccoli"]]
How to Access Elements
Accessing elements is a two-step process using get():
- Get the inner list from the outer list using its index.
- Get the element from the inner list using its index.
// Continuing from the example above... // Access the second category (index 1) List<String> secondCategory = groceryListByCategory.get(1); // This is ["Carrot", "Broccoli"] // Access the first item in the second category (index 0) String firstVegetable = secondCategory.get(0); // This is "Carrot" // You can also chain the calls for a one-off access String secondFruit = groceryListByCategory.get(0).get(1); // This is "Banana"
Common Operations
Iterating with a for-each Loop (Most Common)
This is the cleanest way to loop through all elements.
List<List<Integer>> matrix = List.of(
List.of(1, 2, 3),
List.of(4, 5, 6),
List.of(7, 8, 9)
);
// Outer loop iterates through each inner list
for (List<Integer> row : matrix) {
// Inner loop iterates through each element in the current inner list
for (Integer number : row) {
System.out.print(number + " "); // Prints: 1 2 3 4 5 6 7 8 9
}
System.out.println(); // For a new line after each row
}
Getting the Size
size()on the outer list gives you the number of inner lists.size()on an inner list gives you the number of elements in that specific list.
System.out.println("Number of categories: " + groceryListByCategory.size()); // Output: 2
System.out.println("Number of fruits: " + groceryListByCategory.get(0).size()); // Output: 2
Removing Elements
You can remove either an entire inner list or an element from within an inner list.
// Remove an entire inner list (e.g., the vegetables list)
groceryListByCategory.remove(1); // Removes the list at index 1
// Now groceryListByCategory is just: [["Apple", "Banana"]]
// Remove a specific element from an inner list
groceryListByCategory.get(0).remove("Apple"); // Removes "Apple" from the first list
// Now groceryListByCategory is: [["Banana"]]
Practical Example: A Student-Grade System
Imagine you want to store a student's name and a list of their grades.
import java.util.ArrayList;
import java.util.List;
public class StudentGradeSystem {
public static void main(String[] args) {
// The main list will hold all student records
List<List<Object>> studentRecords = new ArrayList<>();
// --- Add Student 1 ---
List<Object> student1 = new ArrayList<>();
student1.add("Alice");
student1.add(20); // Age
student1.add(List.of(95, 88, 91)); // Grades
studentRecords.add(student1);
// --- Add Student 2 ---
List<Object> student2 = new ArrayList<>();
student2.add("Bob");
student2.add(21);
student2.add(List.of(76, 85, 80));
studentRecords.add(student2);
// --- Access and Print Data ---
System.out.println("--- Student Records ---");
for (List<Object> record : studentRecords) {
String name = (String) record.get(0);
int age = (Integer) record.get(1);
List<Integer> grades = (List<Integer>) record.get(2);
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Grades: " + grades);
System.out.println("----------------------");
}
// --- Calculate and Print Average Grade for Alice ---
List<Object> alicesRecord = studentRecords.get(0);
List<Integer> alicesGrades = (List<Integer>) alicesRecord.get(2);
double sum = 0;
for (int grade : alicesGrades) {
sum += grade;
}
double average = sum / alicesGrades.size();
System.out.println("Alice's average grade: " + average);
}
}
Output:
--- Student Records ---
Name: Alice
Age: 20
Grades: [95, 88, 91]
----------------------
Name: Bob
Age: 21
Grades: [76, 85, 80]
----------------------
Alice's average grade: 88.0
When to Use This Structure
A List<List<E>> is perfect for representing:
- 2D Grids or Matrices: Like a spreadsheet, a tic-tac-toe board, or pixel data for an image.
- Tabular Data: Where each inner list represents a row and the elements in that list represent the columns.
- Grouped or Categorized Data: As shown in the grocery example, where each inner list is a category.
- Multi-level Hierarchies: A simple representation of parent-child relationships (e.g., a list of departments, where each department is a list of employees).
Important Considerations
Type Safety with Generics
Using List<List<String>> is much better than List<List> or List<Object>. It provides compile-time type checking, preventing you from accidentally adding an integer to a list that should only contain strings.
Performance
- Random Access: Both
ArrayListandLinkedListprovide O(1) time complexity for getting an element by its index (get(i)). So, accessingmatrix.get(5).get(2)is very fast. - Insertions/Deletions:
- Adding or removing an entire inner list from the middle of the outer
ArrayListis slow (O(n)) because it requires shifting all subsequent elements. - Adding or removing elements from an inner
ArrayListin the middle is also slow (O(n)). - If you need frequent insertions/deletions in the middle, consider using
LinkedListfor the outer or inner lists, but be aware of the performance trade-offs forget()operations.
- Adding or removing an entire inner list from the middle of the outer
"Varying Length" Inner Lists
A List<List<E>> is great, but if your inner lists can have different lengths, be careful when you assume they are all the same size (e.g., when iterating).
List<List<Integer>> irregularMatrix = new ArrayList<>();
irregularMatrix.add(List.of(1, 2));
irregularMatrix.add(List.of(3, 4, 5, 6));
// This loop is safe
for (List<Integer> row : irregularMatrix) {
for (Integer num : row) {
System.out.print(num + " ");
}
}
// Output: 1 2 3 4 5 6
// This loop is NOT safe because it assumes all rows have 2 elements
// for (int i = 0; i < irregularMatrix.size(); i++) {
// System.out.println(irregularMatrix.get(i).get(2)); // Throws IndexOutOfBoundsException on the second row
// }
The Modern Java 9+ List.of()
For creating immutable lists (lists that cannot be changed after creation), List.of() is a fantastic and concise tool.
// Creates an immutable outer list containing two immutable inner lists
List<List<String>> immutableMatrix = List.of(
List.of("A", "B"),
List.of("C", "D", "E")
);
// This will cause an UnsupportedOperationException
// immutableMatrix.add(List.of("F"));
// This will also cause an UnsupportedOperationException
// immutableMatrix.get(0).add("Z"); 