There cannot be a simpler solution:

  • To know the duplicated ids, you must iterate over the entire collection.
  • To print all the persons with duplicated ids, you must keep their full list.

As such, you will need to load the entire collection of persons in memory. There's no way around that. If you needed only the duplicate ids but not the Person objects, then you could keep just the ids with their counts, and throw away the Person objects as you go after use, that would be more efficient. (But that's not the case here.)

In any case, your solution can be more concise if you skip the intermediary map variable with the mapping of ids to lists of users:

people.stream()
        .collect(Collectors.groupingBy(Person::getId)).values().stream()
        .filter(peopleWithSameId -> peopleWithSameId.size() > 1)
        .forEach(peopleWithSameId -> System.out.println("People with identical IDs: " + peopleWithSameId));

Btw, in case you're wondering if the .stream() there could be .parallelStream(), it would be pointless, due to the synchronization in the println method of System.out (a PrintStream). (And without synchronization println wouldn't be thread safe anyway.)

Answer from janos on Stack Exchange
🌐
DZone
dzone.com › coding › languages › how to compare list objects in java 7 vs. java 8
How to Compare List Objects in Java 7 vs. Java 8
June 1, 2018 - If any number from aList is present in List 2 :true If any number from aList is not present in List 2 :false If all numbers from aList are present in List 2 :true · If you enjoyed this article and want to learn more about Java Streams, check out this collection of tutorials and articles on all things Java Streams. Java (programming language) Object (computer science) Stream (computing) Element
🌐
Baeldung
baeldung.com › home › java › java list › determine if all elements are the same in a java list
Determine If All Elements Are the Same in a Java List | Baeldung
April 4, 2025 - The Stream API’s allMatch() method provides a perfect solution to determine whether all elements of this stream match the provided predicate: public boolean verifyAllEqualAnotherUsingStream(List<String> list) { return list.isEmpty() || ...
Top answer
1 of 3
10

There cannot be a simpler solution:

  • To know the duplicated ids, you must iterate over the entire collection.
  • To print all the persons with duplicated ids, you must keep their full list.

As such, you will need to load the entire collection of persons in memory. There's no way around that. If you needed only the duplicate ids but not the Person objects, then you could keep just the ids with their counts, and throw away the Person objects as you go after use, that would be more efficient. (But that's not the case here.)

In any case, your solution can be more concise if you skip the intermediary map variable with the mapping of ids to lists of users:

people.stream()
        .collect(Collectors.groupingBy(Person::getId)).values().stream()
        .filter(peopleWithSameId -> peopleWithSameId.size() > 1)
        .forEach(peopleWithSameId -> System.out.println("People with identical IDs: " + peopleWithSameId));

Btw, in case you're wondering if the .stream() there could be .parallelStream(), it would be pointless, due to the synchronization in the println method of System.out (a PrintStream). (And without synchronization println wouldn't be thread safe anyway.)

2 of 3
10

Your code and Java 8 usage looks fine in general to me.

I do see an issue with the Person class, it looks like you are intending it to be an immutable class, if so, then you should also enforce it.

You need to ensure that the name and id fields can never be changed, you can do this by adding final to them. Your code currently seems to be safe, but it is not. I can extend Person and offer a method there to change the name and id fields, which violates the assumed variant of that those fields in Person are immutable.

Simply changing it to the following will do:

public class Person {
    private final String name;
    private final String id;
    ...
}

Onto the Java 8 usage now.

It is a good thing that you use the Collectors.groupingBy to provide a Map<String, List<Person>>, you cannot do it much faster either way if you want it to work with any kind of List<Person> as input and in this way you'll save yourself from nasty bugs and reimplementing what lots of people have already done, namely the grouping by operation.

Printing the offending values using Stream seems fine as well, except that you may rewrite it to look a little bit cleaner, something like this could work:

peopleById.values().stream()
    .filter(personList -> personList.size() > 1)
    .forEach(personList -> System.out.println("People with identical IDs: " + personList);

This is my personal preference on how to format it though, the only real change is to rename peopleWithSameId to personList, as it is simply a List<Person> and nothing more or less.

You've done a good job overall.

🌐
WeTalkIt
wetalkitorg.wordpress.com › 2020 › 02 › 03 › how-to-compare-two-lists-in-java
How to compare two Lists in Java - WeTalkIt - WordPress.com
February 25, 2020 - In this paragraph, we will use a very powerfull method to compare two lists of elements in just a few line of code. The containsAll method will check, if all our employees (intern and extern) are also contain in the list of just intern employees. It returns false, when there are some employees (extern), that are not in the list of intern employees and true when we just have intern employees in the list allEmployees. Another to resolve this problem is the use of of the Java Stream Api.
Top answer
1 of 3
4

You could use IntStream

IntStream.range(0, listA.size())
    .map(index -> 
        new RecordB(listA.get(index).getId(), listA.get(index).getValue(),  listA.get(index).getValue() - (index > 0 ? listA.get(index - 1).getValue() : 0))
    )
    .collect(Collectors.toList())
    
2 of 3
0

"... I would like to avoid the class for loop and the previous_val variable. Any ideas how to do this with streams?"

This is a somewhat un-intuitive approach, I had to looked it up actually.
StackOverflow – Java Stream Using Previous Element in Foreach Lambda.

Typically the use of a stream is to aggregate a set of values, and not necessarily compare and contrast them.
Lesson: Aggregate Operations (The Java Tutorials > Collections).

Here is an example utilizing the Collector class, and the Collector#of method.

Essentially, during the collect, you can retrieve the previous element, from whatever has already been collected.

For the BiConsumer argument, a is your collected elements, thus far.

List<RecordB> l
    = listA.stream()
           .collect(
               Collector.<RecordA, List<RecordB>, List<RecordB>>of(
                   ArrayList::new,
                   (a, b) -> {
                       if (a.isEmpty()) a.add(new RecordB(b.id, b.value, 0));
                       else {
                           RecordB x = a.get(a.size() - 1);
                           a.add(new RecordB(b.id, b.value, b.value - x.value));
                       }
                   },
                   (a, b) -> {
                       a.addAll(b);
                       return a;
                   },
                   x -> x));

Output

1, 10, 0
2, 15, 5
3, 25, 10
4, 30, 5

On a final note, you may want to get rid of the RecordB class, and just utilize a Map.

Map<RecordA, Integer> m = new LinkedHashMap<>();
RecordA a, b;
m.put(a = listA.get(0), 0);
for (int i = 1, n = listA.size(); i < n; i++)
    m.put(b = listA.get(i), -a.value + (a = b).value);
Top answer
1 of 3
20

Let's run through each part of the code. First, createSharedListViaStream:

public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
    // We create a stream of elements from the first list.
    List<SchoolObj> listOneList = listOne.stream()
    // We select any elements such that in the stream of elements from the second list
    .filter(two -> listTwo.stream()
    // there is an element that has the same name and school as this element,
        .anyMatch(one -> one.getName().equals(two.getName()) 
            && two.getSchool().equals(one.getSchool())))
    // and collect all matching elements from the first list into a new list.
    .collect(Collectors.toList());
    // We return the collected list.
    return listOneList;
}

After running through the code, it does exactly what you want it to do. Now, let's run through createSharedListViaLoop:

public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
    // We build up a result by...
    List<SchoolObj> result = new ArrayList<SchoolObj>();
    // going through each element in the first list,
    for (SchoolObj one : listOne)
    {
    // going through each element in the second list,
        for (SchoolObj two : listTwo)
        {
    // and collecting the first list's element if it matches the second list's element.
            if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
            {
                result.add(one);
            }
        }
    }
    // We return the collected list
    return result;
}

So far, so good... right? In fact, your code in createSharedListViaStream is fundamentally correct; instead, it is your createSharedListViaLoop that may be causing discrepancies in output.

Think about the following set of inputs:
List1 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameC","SchoolZ")]
List2 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameA","SchoolX"), SchoolObj("nameB","SchoolY")]

Here, createSharedListViaStream will return the only element of the first list that appears in both lists: SchoolObj("nameA","SchoolX"). However, createSharedListViaLoop will return the following list: [SchoolObj("nameA","SchoolX"),SchoolObj("nameA","SchoolX")]. More precisely, createSharedListViaLoop will collect the correct object, but it will do so twice. I suspect this to be the reason for the output of createSharedListViaStream to be "incorrect" based on comparison to the output of createSharedListViaLoop.

The reason that createSharedListViaLoop does this duplication is based on the lack of termination of its inner for loop. Although we iterate over all elements of the first list to check if they are present in the second, finding a single match will suffice to add the element to the result. We can avoid redundant element addition by changing the inner loop to the following:

for (SchoolObj one : listOne)
    {
    for (SchoolObj two : listTwo)
    {
        if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
        {
            result.add(one);
            break;
        }
    }
}

Additionally, if you don't want duplicate Objects in your list (by location in memory), you can use distinct like so:

List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());

As a final caution, the above will keep the results distinct in the following scenario:

List<SchoolObj> list = new ArrayList<>();
SchoolObj duplicate = new SchoolObj("nameC", "schoolD");
listOne.add(duplicate);
listOne.add(duplicate);
list.stream().distinct().forEach(System.out::println); 
// prints:
// nameC schoolD

However, it will not work in the following scenario, unless you override the equals method for SchoolObj:

List<SchoolObj> list = new ArrayList<>();
listOne.add(new SchoolObj("nameC", "schoolD"));
listOne.add(new SchoolObj("nameC", "schoolD"));
list.stream().distinct().forEach(System.out::println); 
// prints (unless Object::equals overridden)
// nameC schoolD
// nameC schoolD
2 of 3
9

You can filter in one list if contains in another list then collect.

List<SchoolObj> listCommon = listTwo.stream()
                                         .filter(e -> listOne.contains(e)) 
                                         .collect(Collectors.toList());

You need to override equals() method in SchoolObj class. contains() method you will uses the equals() method to evaluate if two objects are the same.

@Override
public boolean equals(Object o) {
    if (!(o instanceof SchoolObj))
        return false;
    SchoolObj n = (SchoolObj) o;
    return n.name.equals(name) && n.school.equals(school);
}

But better solution is to use Set for one list and filter in another list to collect if contains in Set. Set#contains takes O(1) which is faster.

Set<SchoolObj> setOne = new HashSet<>(listOne);
List<SchoolObj> listCommon = listTwo.stream()
                                     .filter(e -> setOne.contains(e)) 
                                     .collect(Collectors.toList());

You need to override hashCode() method also along with equals() in SchoolObj class for Set#contains.(assuming name and school can't be null)

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + name.hashCode();
    result = prime * result + school.hashCode();
    return result;
}

Here you will get details how to override equals and hashCode in a better way

🌐
How to do in Java
howtodoinjava.com › home › collections framework › java arraylist › how to compare two lists in java
How to Compare Two Lists in Java - HowToDoInJava
September 20, 2023 - If the element is present in the second list, remove it from the first list. After the stream operations, collect the items to a new list.
Top answer
1 of 3
1

It is very important to understand that the streaming api provided by Java is not just an API, but a different programming paradigm as well. When you use streams you need to embrace the paradigm. You tell streams what you want to achieve, but not how you want to achieve it. And, as with many other things, using streams is not always the best solution. Sometimes, using plain old foor loops makes the code easier to read and probably perform better, too.

Using Streams

If you want to use streams in this example, you could do something similar as below.

As far as I can tell, you are trying to find elements that (based on some criteria) are the same. In other words, you want to group elements based on the value of a property of the Model object.

Let's say our Model class looks like this:

public class Model {
    private int id;
    private String name;

    public Model(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

The example below shows how to group elements of a list of models based on their names.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class CompareItems {

    public static void main(String[] args) {
        List<Model> items = Arrays.asList(
                new Model(1, "model_1"), new Model(2, "model_2"), new Model(3, "model_1"),
                new Model(4, "model_3"), new Model(5, "model_2"), new Model(6, "model_2"));

        Map<String, Set<Integer>> result = 
                items.stream()
                .collect(Collectors.groupingBy(Model::getName, Collectors.mapping(Model::getId, Collectors.toSet())));

        System.out.println(result);
    }
}

When you print the result object, you will get this:

{model_1=[1, 3], model_3=[4], model_2=[2, 5, 6]}

In the result object you have the name of a model and ID-s of all model objects that have the same name.

Depending on what you want to do, you can then apply the changes to each group by iterating over the result object.

2 of 3
0

If you have two ArrayLists A and B, you can get

  • A + B with A.addAll(B)
  • A - B with A.removeAll(B)
  • A ∩ B with A.retainAll(B)
  • A ∪ B with copying A to Set A' then A'.addAll(B)

Then you can use streams to process the result if you need to. However getting the desired subset you are interested in (for example items in common or A ∩ B) is really not something I would consider doing with streams.

Find elsewhere
🌐
Baeldung
baeldung.com › home › java › java list › finding the differences between two lists in java
Finding the Differences Between Two Lists in Java | Baeldung
January 28, 2026 - We should also note that if we want to find the common elements between the two lists, List also contains a retainAll method. A Java Stream can be used for performing sequential operations on data from collections, which includes filtering the differences between lists:
🌐
Javaprogramto
javaprogramto.com › 2020 › 04 › how-to-compare-two-arraylist-for-equality-in-java.html
How to compare two ArrayList for equality in Java 8? ArrayList equals() or containsAll() methods works? JavaProgramTo.com
June 13, 2021 - The below program shows how to compare two ArrayList of different objects in Java 8 using stream api.. public class TwoArrayListEqualityExampleInJava8 { public static void main(String[] args) { // list 1 List<String> list1 = new ArrayList<String>(); list1.add("compare"); list1.add("two"); list1.add("lists"); list1.add("in java 8"); // list 2 List<String> list2 = new ArrayList<String>(); list2.add("compare"); list2.add("two"); list2.add("lists"); list2.add("in java 8"); List<String> unavailable = list1.stream().filter(e -> (list2.stream().filter(d -> d.equals(e)).count()) < 1) .collect(Collecto
🌐
Stack Overflow
stackoverflow.com › questions › 68622810 › comparing-two-lists-by-index-using-the-stream-api
java - Comparing two lists by index using the Stream API - Stack Overflow
I have two distinct lists: list1 and list2, and I want to perform an action for each index where the elements have the same getName() value: for (int i = 0; i < 5; i++) { if (list1.get(i).getName().equals(list2.get(i).getName())) { // TODO } } Is there a way to do this using Java streams? I have tried the logic: if (List1().stream().anymatch(x -> List2().stream().anymatch(y -> x.getName().equals(y.getName))))) { // TODO } This works, but first object(index) of list1 is compared with every object(index) of list2.
🌐
Stack Overflow
stackoverflow.com › questions › 70708207 › comparing-elements-within-stream
java - Comparing elements within Stream - Stack Overflow
Assuming that in general case multiple items may have maximum and/or minimum price, it appears that the stream approach does not help to avoid duplicate iteration. Instead, a loop should be used to convert the items into item responses and build two intermediate lists of items with maximum / minimum prices, and after that iterate the smaller lists and set appropriate indicators.
Top answer
1 of 3
7

You can make the code shorter with filter and count, but using loops is cleaner here. Streams are not always the solution.

return Arrays.asList(
    IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) > b.get(i)).count(),
    IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) < b.get(i)).count()
);
2 of 3
3

Java 8 - partitioningBy() & counting()

One of the way to solve this problem with streams performing only one iteration over the given set of data is to make use of the built Collectors partitioningBy() and counting():

public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
    
    return IntStream.range(0, a.size())
        .map(i -> a.get(i) - b.get(i))
        .filter(i -> i != 0)
        .boxed()
        .collect(Collectors.collectingAndThen(
            Collectors.partitioningBy(i -> i > 0, Collectors.counting()),
            map -> Arrays.asList(map.get(true).intValue(), map.get(false).intValue())
        ));
}

Java 8 - custom Collector

Another option would be to define a custom Collector using static factory method Collector.of(). As well as previous approach, it would allow to process the data using a single stream:

public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
    
    return IntStream.range(0, a.size())
        .boxed()
        .collect(Collector.of(
            () -> new int[]{0, 0},
            (int[] score, Integer i) -> {
                if (a.get(i) > b.get(i)) score[0]++;
                if (b.get(i) > a.get(i)) score[1]++;
            },
            (int[] left, int[] right) -> {
                Arrays.setAll(left, i -> left[i] + right[i]);
                return left;
            },
            arr -> Arrays.asList(arr[0], arr[1])
        ));
}

Java 12 - Collector teeing()

Another option that would allow to produce the result using a single stream is Java 12 Collector teeing(), which expects two downstream Collectors and a Function which performs a final transformation by merging the results they produced.

public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
    
    return IntStream.range(0, a.size())
        .boxed()
        .collect(Collectors.teeing(
            Collectors.filtering(i -> a.get(i) > b.get(i), Collectors.counting()),
            Collectors.filtering(i -> b.get(i) > a.get(i), Collectors.counting()),
            (alice, bob) -> List.of(alice.intValue(), bob.intValue())
        ));
}
🌐
amitph
amitph.com › home › java › comparing two lists in java
Comparing Two Lists In Java - amitph
November 22, 2024 - To do that, we can create a Stream of the first List and use the filter() method to retain only the elements in the second List. Example of using Java Streams to find common elements between two Java Lists.
🌐
TutorialsPoint
tutorialspoint.com › how-to-determine-if-all-elements-are-the-same-in-a-java-list
How to determine if all elements are the same in a Java List?
June 10, 2025 - import java.util.ArrayList; import java.util.List; public class SameElements{ public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(1); list.add(1); list.add(1); boolean allSame = true; Integer firstElement = list.get(0); for (Integer element : list) { if (!element.equals(firstElement)) { allSame = false; System.out.println("Found different element: " + element); break; } } System.out.println("All elements are the same: " + allSame); } } ... Now, let's use the Stream API to check if all elements in a list are the same.
Top answer
1 of 3
24

Your question’s code does not reflect what you describe in the comments. In the comments you say that all names should be present and the size should match, in other words, only the order may be different.

Your code is

List<Person> people = getPeopleFromDatabasePseudoMethod();
List<String> expectedValues = Arrays.asList("john", "joe", "bill");

assertTrue(people.stream().map(person -> person.getName())
                 .collect(Collectors.toList()).containsAll(expectedValues));

which lacks a test for the size of people, in other words allows duplicates. Further, using containsAll combining two Lists in very inefficient. It’s much better if you use a collection type which reflects you intention, i.e. has no duplicates, does not care about an order and has an efficient lookup:

Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().map(Person::getName)
                 .collect(Collectors.toSet()).equals(expectedNames));

with this solution you don’t need to test for the size manually, it is already implied that the sets have the same size if they match, only the order may be different.

There is a solution which does not require collecting the names of persons:

Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().allMatch(p->expectedNames.remove(p.getName()))
           && expectedNames.isEmpty());

but it only works if expectedNames is a temporary set created out of the static collection of expected names. As soon as you decide to replace your static collection by a Set, the first solution doesn’t require a temporary set and the latter has no advantage over it.

2 of 3
4

If the number of elements must be the same, then it would be better to compare sets:

List<Person> people = getPeopleFromDatabasePseudoMethod();
Set<String> expectedValues = new HashSet<>(Arrays.asList("john", "joe", "bill"));
assertEquals(expectedValues, 
    people.stream().map(Person::getName).collect(Collectors.toSet()));

The equals method for properly implemented sets should be able to compare different types of sets: it just checks whether the contents is the same (ignoring the order of course).

Using assertEquals is more convenient as in case of failure an error message will contain the string representation of your set.