Java 8 use streams for a "group by" feature I am writing a Java function that checks if the items of a list are unique (depending of an id member).

I have a Java 7 implementation that works, but it is not using streams, filter and joins. I think the code can be improved.

Case 1: empty list => OK

Case 2: all ids are different => OK

+---------+-------+
| baf18c4 | Item1 |
| c5e4623 | Item2 |
| bf1932f | Item3 |
+---------+-------+


No message expected.

Case 3: one id is duplicate (3 times each) => message expected

+---------+-------+
| baf18c4 | Item1 |
| c5e4623 | Item2 |
| baf18c4 | Item3 |
| baf18c4 | Item4 |
+---------+-------+


Expected message:


  Some of the items are sharing the same code: 'baf18c4' used by {Item1, Item3, Item4}


Case 4: two ids is duplicate (2 times each) => message expected

+---------+-------+
| baf18c4 | Item1 |
| c5e4623 | Item2 |
| baf18c4 | Item3 |
| bf1932f | Item4 |
| c5e4623 | Item5 |
+---------+-------+



  Some of the items are sharing the same code: 'baf18c4' used by {Item1, Item3}, 'c5e4623' used by {Item2, Item5}


Code

package tmp;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.junit.Test;

public class Snippet {
  private static String verifyItems(List&lt;Item&gt; items) {
    Map&lt;String, List&lt;Item&gt;&gt; map = new HashMap&lt;&gt;();
    for (Item item : items) {
      String key = item.getCode();
      List&lt;Item&gt; list;
      if (map.containsKey(key)) {
        list = map.get(key);
      }
      else {
        list = new ArrayList&lt;&gt;();
        map.put(key, list);
      }
      list.add(item);
    }
    boolean isFirst = true;
    StringBuilder sb = new StringBuilder();
    for (Entry&lt;String, List&lt;Item&gt;&gt; e : map.entrySet()) {
      if (e.getValue().size() &gt; 1) {
        if (isFirst) {
          sb.append("Some of the items are sharing the same code: ");
        }
        else {
          sb.append(", ");
        }
        isFirst = false;
        sb.append("'" + e.getKey() + "' used by ");
        sb.append(e.getValue().stream().map(i -&gt; i.getDescription())
            .collect(Collectors.joining(", ", "{", "}")));
      }
    }
    return sb.toString();
  }

  @Test
  public void testEmpty() throws Exception {
    assertEquals("", verifyItems(Collections.emptyList()));
  }

  @Test
  public void testOk() throws Exception {
    List&lt;Item&gt; items = Arrays.asList(
        new Item("baf18c4", "Item1"),
        new Item("c5e4623", "Item2"),
        new Item("bf1932f", "Item3"));
    assertEquals("", verifyItems(items));
  }

  @Test
  public void testDuplicate() throws Exception {
    List&lt;Item&gt; items = Arrays.asList(
        new Item("baf18c4", "Item1"),
        new Item("c5e4623", "Item2"),
        new Item("baf18c4", "Item3"),
        new Item("baf18c4", "Item4"));
    assertEquals("Some of the items are sharing the same code: 'baf18c4' used by {Item1, Item3, Item4}", verifyItems(items));
  }

  @Test
  public void testDuplicate2() throws Exception {
    List&lt;Item&gt; items = Arrays.asList(
        new Item("baf18c4", "Item1"),
        new Item("c5e4623", "Item2"),
        new Item("baf18c4", "Item3"),
        new Item("bf1932f", "Item4"),
        new Item("c5e4623", "Item5"));
    assertEquals("Some of the items are sharing the same code: 'baf18c4' used by {Item1, Item3}, 'c5e4623' used by {Item2, Item5}", verifyItems(items));
  }

  public static class Item {
    private String code;
    private String description;

    public Item(String code, String description) {
      super();
      this.code = code;
      this.description = description;
    }

    public String getCode() {
      return code;
    }

    public String getDescription() {
      return description;
    }
  }
}
Thank you for a nicely formulated and formatted first question! :)

Indeed, the same result can be achieved with much less code using Java 8.

I suggest the following:


Group all the items into a Map&lt;String, List&lt;Item&gt;&gt;, equivalent of the first original for loop:

Map&lt;String, List&lt;Item&gt;&gt; itemsGroupedByCode = 
  items.stream().collect(Collectors.groupingBy(Item::getCode));

Collect all the messages citing duplicate entries, equivalent of the second original for loop, but without the prefix:

String duplicatesMessage =
  itemsGroupedByCode.entrySet().stream()
      .filter(entry -&gt; entry.getValue().size() &gt; 1)
      .map(entry -&gt; {
        final String duplicateValues = entry.getValue().stream()
                                                       .map(Item::getDescription)
                                                       .collect(Collectors.joining(", ", "{", "}"));
        return String.format("'%1$s' used by %2$s", entry.getKey(), duplicateValues);
  }).collect(Collectors.joining(", "));

Return the result, empty if there were no duplicates or prefixed if there were some entries:

if (!duplicatesMessage.isEmpty()) {
  return "Some of the items are sharing the same code: " + duplicatesMessage;
}
return "";



I'd also suggest to change the return type of the method to Optional&lt;String&gt; and return Optional.empty() instead of the empty string. But this is up to you to decide if it fits the context of your app.