Решение YCF_L должно работать, но это решение O (n2). Решение O (n) может быть достигнуто путем преобразования одного списка в карту с идентификатора на объект, а затем итерации по другому и получения соответствующего значения с карты:
Map<Integer, Person> personMap =
persons.stream().collect(Collectors.toMap(Person::getId, Function.identity());
List<PersonWithMetadata> result =
metadata.stream()
.map(m -> new PersonWithMetadata(personMap.get(m.getId()), m)
.collect(Collectors.toList());
В образце данных, списки имеют соответствующие объекты в порядке совпадения. Если это предположение верно и для реальной проблемы, решение может быть проще: вы можете передавать индексы и получать соответствующие значения из списков:
List<PersonWithMetadata> result =
IntStream.reange(0, persons.size())
.map(i -> new PersonWithMetadata(persons.get(i), metadata.get(i))
.collect(Collectors.toList());