eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

List is a pretty commonly used data structure in Java. Sometimes, we may need a nested List structure for some requirements, such as List<List<T>>.

In this tutorial, we’ll take a closer look at this “List of Lists” data structure and explore some everyday operations.

2. List Array vs. List of Lists

We can look at the “List of Lists” data structure as a two-dimensional matrix. So, if we want to group a number of List<T> objects, we have two options:

  • Array-based: List<T>[]
  • List-based: List<List<T>>

Next, let’s have a look at when to choose which one.

Array is fast for “get” and “set” operations, which run in O(1) time. However, since the array’s length is fixed, it’s costly to resize an array for inserting or deleting elements.

On the other hand, List is more flexible on insertion and deletion operations, which run in O(1) time. Generally speaking, List is slower than Array on “get/set” operations. But some List implementations, such as ArrayList, are internally based on arrays. So, usually, the difference between the performance of Array and ArrayList on “get/set” operations is not noticeable.

Therefore, we would pick the List<List<T>> data structure in most cases to gain better flexibility.

Of course, if we’re working on a performance-critical application, and we don’t change the size of the first dimension – for instance, we never add or remove inner Lists – we can consider using the List<T>[] structure.

We’ll mainly discuss List<List<T>> in this tutorial.

3. Common Operations on List of Lists

Now, let’s explore some everyday operations on List<List<T>>.

For simplicity, we’ll manipulate the List<List<String>> object and verify the result in unit test methods.

Further, to see the change straightforwardly, let’s also create a method to print the content of the List of Lists:

private void printListOfLists(List<List<String>> listOfLists) {
    System.out.println("\n           List of Lists          ");
    System.out.println("-------------------------------------");
    listOfLists.forEach(innerList -> {
        String line = String.join(", ", innerList);
        System.out.println(line);
    });
}

Next, let’s first initialize a list of lists.

3.1. Initializing a List of Lists

We’ll import data from a CSV file into a List<List<T>> object. Let’s first look at the CSV file’s content:

Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

Let’s say we name the file as example.csv and put it under the resources/listoflists directory.

Next, let’s create a method to read the file and store the data in a List<List<T>> object:

private List<List<String>> getListOfListsFromCsv() throws URISyntaxException, IOException {
    List<String> lines = Files.readAllLines(Paths.get(getClass().getResource("/listoflists/example.csv")
        .toURI()));

    List<List<String>> listOfLists = new ArrayList<>();
    lines.forEach(line -> {
        List<String> innerList = new ArrayList<>(Arrays.asList(line.split(", ")));
        listOfLists.add(innerList);
    });
    return listOfLists;
}

In the getListOfListsFromCsv method, we first read all lines from the CSV file into a List<String> object. Then, we walk through the lines List and convert each line (String) into List<String>.

Finally, we add every converted List<String> object to listOfLists. Thus, we’ve initialized a list of lists.

Curious eyes may have detected that we wrap Arrays.asList(..) in a new ArrayList<>(). This is because the Arrays.asList method will create an immutable List. However, we’ll make some changes to the inner lists later. Therefore, we wrap it in a new ArrayList object.

If the list of lists object is created correctly, we should have three elements, which is the number of lines in the CSV file, in the outer list.

Moreover, each element is an inner list, and each of those should contain four elements. Next, let’s write a unit test method to verify this. Also, we’ll print the initialized list of lists:

List<List<String>> listOfLists = getListOfListsFromCsv();

assertThat(listOfLists).hasSize(3);
assertThat(listOfLists.stream()
  .map(List::size)
  .collect(Collectors.toSet())).hasSize(1)
  .containsExactly(4);

printListOfLists(listOfLists);

If we execute the method, the test passes and produces the output:

           List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Delete Me, Mercurial, Git, Subversion

Next, let’s so make some changes to the listOfLists object. But, first, let’s see how to apply changes to the outer list.

3.2. Applying Changes to the Outer List

If we focus on the outer list, we can ignore the inner list at first. In other words, we can look at List<List<String>> as the regular List<T>.

Thus, it’s not a challenge to change a regular List object. We can call List‘s methods, such as add and remove, to manipulate the data.

Next, let’s add a new element to the outer list:

List<List<String>> listOfLists = getListOfListsFromCsv();
List<String> newList = new ArrayList<>(Arrays.asList("Slack", "Zoom", "Microsoft Teams", "Telegram"));
listOfLists.add(2, newList);

assertThat(listOfLists).hasSize(4);
assertThat(listOfLists.get(2)).containsExactly("Slack", "Zoom", "Microsoft Teams", "Telegram");

printListOfLists(listOfLists);

An element of the outer list is actually a List<String> object. As the method above shows, we create a list of popular online communication utilities. Then, we add the new list to listOfLists in the position with index=2.

Again, after the assertions, we print the content of listOfLists:

           List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS, Delete Me
Kotlin, Delete Me, Java, Python
Slack, Zoom, Microsoft Teams, Telegram
Delete Me, Mercurial, Git, Subversion

3.3. Applying Changes to Inner Lists

Finally, let’s see how to manipulate the inner lists.

Since listOfList is a nested List structure, we need to first navigate to the inner list object we want to change. If we know the index exactly, we can simply use the get method:

List<String> innerList = listOfLists.get(x);
// innerList.add(), remove() ....

However, if we would like to apply a change to all inner lists, we can pass through the list of lists object via a loop or the Stream API.

Next, let’s see an example that removes all “Delete Me” entries from the listOfLists object:

List<List<String>> listOfLists = getListOfListsFromCsv();

listOfLists.forEach(innerList -> innerList.remove("Delete Me"));

assertThat(listOfLists.stream()
    .map(List::size)
    .collect(Collectors.toSet())).hasSize(1)
    .containsExactly(3);

printListOfLists(listOfLists);

As we’ve seen in the method above, we iterate each inner list via listOfLists.forEach{ … } and use a lambda expression to remove “Delete Me” entries from innerList.

If we execute the test, it passes and produces the following output:

           List of Lists           
-------------------------------------
Linux, Microsoft Windows, Mac OS
Kotlin, Java, Python
Mercurial, Git, Subversion

3.4. The Size of the List of Lists

We know the standard List.size() method returns the count of the elements in the list. However, when talking about the size of a list of lists, depending on the requirement, there can be two scenarios:

  • The number of inner lists
  • The sum of the number of elements in all inner lists

Next, let’s see how to calculate the two different sizes through examples.

Since inner lists are elements of the outer list, we can simply call the listOfLists.size() method to get the number of inner lists:

List<List<String>> listOfLists = getListOfListsFromCsv();
// the number of inner lists
assertThat(listOfLists).hasSize(3);

However, to get all elements in inner lists, we must sum up each inner list’s size. We can write a loop to access each inner list and sum their size. A better way is to use Stream API to get the result:

// size of all elements in inner lists
int totalElements = listOfLists.stream().mapToInt(List::size).sum();
assertThat(totalElements).isEqualTo(12);

As the example above shows, we transform each inner list into an integer using the mapToInt() method. The integer is the size of each inner list. Further, as mapToInt() returns an IntStream, we can get the sum of the IntStream by calling the sum() method.

4. Conclusion

In this article, we’ve discussed the list of lists data structure.

Further, we’ve addressed the common operations on the list of lists through examples.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)