find matches between attributes from two different Objects I'm new in Java 8, and I'm trying to convert and optimize my method from java 6 to java 8. I want it to be less verbose, with fewer lines of code.
This method gets an object (userRequest) and tries to find from a list of other object type (User) the one with more matches between their attributes(for example, if the user requested has brown eyes, each user from the list who has brown eyes has one more match). If it results that there is more than one User, so the one with less age is chosen.
In this example it's only used a list of 3 users and only 3 or 4 attributes, but in the future it's intended to be more, so I'd like to optimize it for multiple users and attributes.
@Getter
@Setter
@AllArgsConstructor
@ToString
public class UserRequest{
    private String name;
    private String surname;
    private String company;
    private String eyesColor;
}

@Getter
@Setter
@AllArgsConstructor
@ToString
public class User{
    private Long id;
    private String name;
    private String surname;
    private String eyesColor;
    private int age;
}

@Service
public class UserServiceImpl implements UserService{
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private UserFilters userFilters;
    
    public int getMostMatchingUserId(UserRequest userRequest){
        
        List&lt;User&gt; userList = userRepository.getUsers();
        
        List&lt;User&gt; mostMatchingUserList = userFilters.getMostMatchingUserList(userList, userRequest);
        
        return mostMatchingUserList.stream().min(Comparator.comparing(User::getAge)).orElse(mostMatchingUserList.get(0)).getId();
    }
}

@UtilityClass
public class UserFilters{

    public List&lt;User&gt; getMostMatchingUserList (List&lt;User&gt; userList, UserRequest userRequest) {

    List&lt;User&gt; newUserList = new ArrayList&lt;&gt;();
    int matchesInitialCount = 0;
    
    for(User user: userList){
        int userMatchesCount = countMatches(user, userRequest);
        if(userMatchesCount &gt; matchesInitialCount){
            newUserList.clear();
            newUserList.add(user);
            matchesInitialCount = userMatchesCount;
        } else if(userMatchesCount == matchesInitialCount){
            newUserList.add(user);
        }
    }
    return newUserList;
    }
    
    private int countMatches (User user, UserRequest userRequest) {
        int matchesCounted = 0;
        
        if (Objects.nonNull(user.getName()) &amp;&amp; user.getName().equals(userRequest.getName())) {
            matchesCounted++;
        } 
        if (Objects.nonNull(user.getSurname()) &amp;&amp; user.getSurname().equals(userRequest.getSurname())) {
            matchesCounted++;
        } 
        if (Objects.nonNull(user.getAge()) &amp;&amp; user.getAge() == userRequest.getAge() {
            matchesCounted++;
        } 
        return matchesCounted;
    }
}

Imagine you get a new userRequest (&quot;John&quot;, &quot;Jackson&quot;, &quot;aena&quot;, &quot;brown&quot;) to check which one of the next userList has the most matches:
User1 (111, &quot;John&quot;, &quot;Wine&quot;, &quot;blue&quot;, 37);
User2 (222, &quot;Maria&quot;, &quot;Jackson&quot;, &quot;brown&quot;, 25);
User3 (333, &quot;Michel&quot;, &quot;Jackson&quot;, &quot;brown&quot;, 55);
The result is: User1 has one match and User2 &amp; User3 have two matches each. The next validation checks who is the youngest one from User2 &amp; User3: so User2 will be the chosen one.
First off, a bug (or probably just a copy/paste error): countMatches is comparing age instead of eye color.
Then, any reason you are limited to Java 8? At the very least you should be using the LTS release 11, or better the current release 15.
Also, you have some minor formatting issues that don't confirm with standard Java conventions. Mostly it's wrong spacing: there should be spaces before { and between keywords (e.g. if) and (, but no spaces between method names and (.
Lombok
The annotations on the data classes could be most likely simplified to @Data @AllArgsConstructor. And you should even consider using @Value instead for immutable objects when ever possible, which can help avoiding bugs.
Spring DI
Generally autowiring private properties has been discouraged. You should prefer constructor injection. With Lombok's @RequiredArgsConstructor it becomes quite easy:
@Service
RequiredArgsConstructor
public class UserServiceImpl implements UserService{
    
    private final UserRepository userRepository;
    
    private final UserFilters userFilters;

    //....
}

Since you are injecting UserFilters it shouldn't be static i.e. @UtilityClass.
For a matter of fact I don't see why UserFilters is a separate class at all. Those methods don't look like they should be used outside of UserService, so they should belong as (private) methods in UserService.
Method UserServiceImpl.getMostMatchingUserId
The name isn't quite right and doesn't give enough information in my opinion. It's not a &quot;getter&quot;, so it should be called find... and nt get..., the meaning &quot;Most matching&quot; needs to be defined in a comment and the word &quot;youngest&quot; needs to be added.
Also it seems quite limiting to have the method return just the user ID.
One problem in here is the use of orElse on the result of min(). min() only returns an empty Optional, if the stream is empty, so in that case .get(0) will throw an exception.
A final though here, is that normally one would expect that the repository (or usually the database behind it) should filter the list of results. If the database would do the filtering, then this would scale better with more users.
Method UserFilters.getMostMatchingUserList
Also not a &quot;getter&quot;. filter... would be the most appropriate prefix here.
One could consider using a stream here, however in this specific case I don't think it would make the task much simpler/better to read. The only change I'd make here would be choose some better variable names: newUserList should be called filteredUserList and matchesInitialCount should be currentMatchesCount.
Method UserFilters.countMatches
This implementation is fine. Personally I'd leave it like this, and just add/change more property checks when needed, unless the list of properties needs to changed quickly or easily, for example, via configuration. It that case you need to work with reflection to find the properties.