A semi-join answers "which of these appear over there" without pulling any column across. Collect the other stream's keys into a Set once; the filter is then a hash lookup per row.
record User(String id, String name) {}
record Login(String userId, String at) {}
var users = List.of(new User("u1", "Ava"), new User("u2", "Ben"), new User("u3", "Cara"));
var logins = List.of(new Login("u1", "09:00"), new Login("u3", "09:05"), new Login("u1", "09:40"));
Set<String> loggedIn = logins.stream().map(Login::userId).collect(Collectors.toSet());
users.stream()
.filter(u -> loggedIn.contains(u.id()))
.map(User::name)
.forEach(System.out::println);
Ava
Cara
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