Search code examples
javaspring-bootjpaspring-data-jpajava-17

Required Type - capture of?


I am stuck on an issue that involves generics. I understand the problem, but I do not know how call methods for JpaRepository. For instance, when I call existsById and pass a Long number value, I get capture of ? is required.

Below lies a snippet of the code:

private JpaRepository<?, ?> getRepository(Object repository) {
    if (repository instanceof JpaRepository<?, ?> repo) {
        return repo;
    }
    return null;
}

The repository:

@Repository
public interface IStatusRepository extends JpaRepository<Status, Long> {
}

The issue come here, when I try to call most methods from getRepository(...):

public void test(IStatusRepository statusRepository) {
    ...
    var data = getRepository(statusRepository).findById(1L);
    ...
}

The code above results in a compilation error. The findById(1L); now takes a "capture of ?" and passing a long does not satisfy the compiler.

How can I call findById with no compilation error?


Solution

  • Problem

    Generic parameters don't exist at runtime. At runtime, the type of JpaRepository<Status, Long> is simply JpaRepository.

    When you check if (repository instanceof JpaRepository<?, ?> repo) the check that is actually made is if it's an instance of JpaRepository, and that's it.

    So what it returns can be a JpaRepository with any type arguments, hence the ? wildcards to say that the type is unknown. From a type perspective, it doesn't mean that the returned type has the same type arguments as repository.

    Solution (?)

    As for how to satisfy this... Do you actually need to call getRepository? This function seems very hacky, and in your example, is not even needed, as you can call statusRepository.findById(1L) directly.

    For the sake of providing a solution, I'll give you one, but I strongly suggest against doing this.

    You could get rid of the compilation error by using raw type of JpaRepository :

    // DO NOT DO THIS
    private JpaRepository getRepository(Object repository) {
        if (repository instanceof JpaRepository repo) {
            return repo;
        }
        return null;
    }
    

    This would get rid of the compilation error (you'll likely get a warning), but then you open the way of having runtime errors (ClassCastExceptions) because you can assign the result to any type (JpaRepository<String, String> data = getRepository(statusRepository); would compile for example), which is way more dangerous than compile errors. You basically remove type safety here.