Multi-stream: anti-join finds rows with no match on the other side

Invert the semi-join and you have the report every operations team asks for first: orders with no payment, users with no login, events with no acknowledgement.

Code
record Order(String ref, double amount) {}
record Payment(String orderRef, double paid) {}

var orders = List.of(new Order("A-1", 250.0), new Order("A-2", 90.0), new Order("A-3", 40.0));
var payments = List.of(new Payment("A-1", 250.0), new Payment("A-3", 40.0));

Set<String> settled = payments.stream().map(Payment::orderRef).collect(Collectors.toSet());

var unpaid = orders.stream().filter(o -> !settled.contains(o.ref())).toList();
System.out.println("unpaid: " + unpaid);
System.out.println("owed  : " + unpaid.stream().mapToDouble(Order::amount).sum());
Output
unpaid: [Order[ref=A-2, amount=90.0]]
owed  : 90.0
Advertisement
More in JAVA

Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.

Published 2026-09-20

© Java Coding Hub · About · Contact · Privacy · Terms