ZetCode

Converting List to HashMap in Java

Last modified June 1, 2025

This tutorial explores multiple techniques to convert a List to a HashMap in Java, covering traditional for-loops, the modern Stream API, and advanced scenarios like handling custom objects, duplicate keys, and grouping elements. These methods are essential for transforming data structures in Java applications, such as mapping indices to elements or grouping objects by properties.

Basic Conversion with For-Loop

A straightforward approach to convert a List to a HashMap is using a for-loop, mapping list indices to elements. This method is simple and suitable for small lists or when explicit control is needed.

Main.java
void main() {

    List<String> fruits = List.of("Apple", "Banana", "Cherry", "Date");
    Map<Integer, String> map = new HashMap<>();
    
    for (int i = 0; i < fruits.size(); i++) {
        map.put(i + 1, fruits.get(i)); // Start indexing from 1
    }
    
    System.out.println(map);
}

This example uses List.of to create an immutable list of fruits, then initializes a HashMap to store index-element pairs. A for-loop iterates over the list, mapping indices starting from 1 to each fruit, producing a map where each fruit is associated with a unique index. This approach is explicit and easy to understand, making it ideal for simple conversions, though it can become verbose for complex transformations.

Using Stream API

The Stream API provides a functional and concise way to convert a List to a HashMap. Using IntStream and Collectors.toMap, you can achieve the same result as the for-loop with less code.

Main.java
void main() {

    List<String> fruits = List.of("Apple", "Banana", "Cherry", "Date");
    
    Map<Integer, String> map = IntStream.rangeClosed(1, fruits.size())
        .boxed()
        .collect(Collectors.toMap(
            i -> i,
            i -> fruits.get(i - 1)
        ));
    
    System.out.println(map);
}

In this example, IntStream.rangeClosed(1, fruits.size()) generates a sequence of indices from 1 to the size of the list, which is then converted to a Stream<Integer> using boxed() to enable use with Collectors.toMap.

The toMap collector maps each index to the corresponding list element accessed via fruits.get(i - 1), producing the same result as the for-loop example. This method is more concise and aligns with functional programming principles, offering flexibility for complex mappings, though it may introduce slight overhead for very small lists due to stream setup.

Converting List of Custom Objects

When working with a List of custom objects, you can use the Stream API to map objects to a HashMap based on a specific field as the key.

Main.java
record Product(int id, String name, double price) {}

void main() {

    List<Product> products = List.of(
        new Product(101, "Laptop", 999.99),
        new Product(102, "Phone", 699.99),
        new Product(103, "Tablet", 499.99)
    );
    
    Map<Integer, Product> map = products.stream()
        .collect(Collectors.toMap(
            Product::id,
            p -> p,
            (existing, replacement) -> existing // Keep existing entry on duplicate keys
        ));
    
    map.forEach((k, v) -> System.out.printf("ID: %d, Product: %s%n", k, v));
}

This example defines a Product record with id, name, and price fields, and creates a list of product instances. The stream operation uses Collectors.toMap to map each product's id to the product object itself, with a merge function (existing, replacement) -> existing to handle potential duplicate keys by retaining the first occurrence.

The resulting map associates each product's ID with the corresponding product, which is useful for scenarios like mapping database records to their primary keys. The output is formatted to display each key-value pair clearly.

Handling Duplicate Keys

When converting a List to a HashMap, duplicate keys may arise. The Stream API's toMap collector allows a merge function to resolve conflicts, such as counting occurrences.

Main.java
void main() {

    List<String> fruits = List.of("Apple", "Banana", "Apple", "Cherry", "Apple");
    
    Map<String, Integer> map = fruits.stream()
        .collect(Collectors.toMap(
            f -> f,
            f -> 1,
            Integer::sum
        ));
    
    System.out.println(map);
}

This example processes a list of fruits with duplicates, using Collectors.toMap to create a map where each fruit is a key and its value represents the number of occurrences. Each fruit is initially mapped to a count of 1, and the merge function Integer::sum adds the counts for duplicate keys, resulting in a frequency map (e.g., "Apple" appears three times).

This approach is ideal for counting occurrences or aggregating data. Without the merge function, attempting to map duplicate keys would throw an IllegalStateException, making the merge function essential for handling duplicates gracefully.

Grouping List Elements by Property

The Collectors.groupingBy method groups list elements into a Map where the key is a property, and the value is a List of matching objects.

Main.java
record Person(String name, int age, String department) {}

void main() {

    List<Person> people = List.of(
        new Person("John", 25, "IT"),
        new Person("Jane", 30, "HR"),
        new Person("Bob", 25, "IT"),
        new Person("Alice", 28, "HR")
    );
    
    Map<String, List<Person>> byDept = people.stream()
        .collect(Collectors.groupingBy(Person::department));
    
    byDept.forEach((dept, list) -> 
        System.out.printf("%s: %s%n", dept, list.stream()
            .map(p -> p.name())
            .collect(Collectors.joining(", ")))
    );
}

This example uses Collectors.groupingBy to group a list of Person objects by their department field, producing a Map where each key is a department and the value is a list of people in that department.

The output is formatted to display only the names of people in each department, joined by commas for readability. This approach is particularly useful for categorizing data, such as grouping employees by department or products by category, making it easier to analyze or display grouped information.

Performance Considerations

Method Use Case Performance Pros Cons
For-loop Simple index-based mapping O(n), minimal overhead Fast for small lists, explicit control Verbose, error-prone for complex logic
Stream API with toMap Functional transformations O(n), slight stream overhead Concise, flexible for complex mappings Slower for small lists due to stream setup
Collectors.toMap Key-value mappings O(n), efficient for direct mappings Handles duplicates with merge function Requires careful key uniqueness handling
Collectors.groupingBy Grouping by property O(n), depends on grouping complexity Ideal for categorization tasks More memory for storing grouped lists

For small lists (<100 elements), for-loops are often faster due to lower overhead.Streams shine in readability and maintainability for complex transformations or parallel processing.

Use LinkedHashMap instead of HashMap in toMap to preserve insertion order if needed. Avoid null keys/values in toMap to prevent NullPointerException.

Source

Java Collectors Documentation

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.