8

I have the following interface:

public interface UserRepository<T extends User> {
    List<T> findAll(UserCriteria userCriteria, PageDetails pageDetails);
    T findByEmail(String email);
}

And its implementation:

@Repository
public class JpaUserRepository implements UserRepository<JpaUser> {
    public List<JpaUser> findAll(UserCriteria userCriteria, PageDetails pageDetails) {
       //implementation
    }

    public JpaUser findByEmail(String email) {
       //implementation
    }
}

Now, when I call:

User user = userRepository.findByEmail(email);

in my service class, everything is fine.

But when I call:

List<User> users = userRepository.findAll(userCriteria, pageDetails);

I get unchecked assignment warning with a reason that userRepository has raw type, so result of findAll is erased. If this is indeed the case, shouldn't findByEmail behave the same? It just doesn't seem very consistent.

How can I eliminate the raw type in this scenario? I've tried few things:

Removing <T extends User> from interface and applying it to method like this:

<U extends User> List<U> findAll(UserCriteria userCriteria, PageDetails pageDetails);

That works fine for the service, but repository implementation now gives warning about unchecked overriding (return type requires unchecked conversion).

I've also tried removing generics from interface and method, while keeping the return list generic:

List<? extends User> findAll(UserCriteria userCriteria, PageDetails pageDetails);

That solves the problem, no warnings, but requires me to write the service like this:

List<? extends User> users = userRepository.findAll(userCriteria, pageDetails);

And it feels a bit clunky (maybe it's just me, so please, let me know if this is acceptable from "good programming" perspective).

Either way, is it possible to get List<User> without raw type warnings, while keeping repository untouched?

Thanks a lot for your time.

EDIT: I am not looking for a way to cast the list.

EDIT 2: Repository declaration:

private final UserRepository userRepository;

Some of you suggested to change that declaration to UserRepository<User> userRepository; and thats successfully removes the warnings but Spring cannot find the bean to autowire this way as JpaUserRepository is UserRepository<JpaUser>. Service layer does not know about repository implementation.

Sikor
  • 11,628
  • 5
  • 28
  • 43
  • 3
    How is `userRepository` declared? – ernest_k Apr 28 '18 at 18:43
  • https://stackoverflow.com/a/5082088/9046830 – Bogdan Lukiyanchuk Apr 28 '18 at 18:49
  • You should use `List extends User> users = userRepository.findAll(userCriteria, pageDetails);`. You should not be able to add an instance of `User` that is not a `JpaUser` to a `List`. – Johannes Kuhn Apr 28 '18 at 18:50
  • Possible duplicate of [Most efficient way to cast List to List](https://stackoverflow.com/questions/5082044/most-efficient-way-to-cast-listsubclass-to-listbaseclass) – Johannes Kuhn Apr 28 '18 at 18:50
  • @ErnestKiwele Your assumptions about my declaration were correct, however if I do this, my application won't start because Spring cannot find a bean to autowire. – Sikor Apr 28 '18 at 19:01

2 Answers2

4

It would help if you showed how userRepository is declared, because that is missing. But the issue is that the repository has the generic type <T extends User>, and this is not the same as <User>, which is why your code is giving warnings.

The question is not so much about the warnings, as it is about the proper usage of generic types. I'm guessing that your userRepository is declared as follows:

@Autowired
private JpaUserRepository userRepository;

But that means that the name is wrong. After all, it's not a repository of User objects, but of JpaUser objects.

Now, you may argue that JpaUser is derived from User. But that's not how Java generics work. There's an excellent resource out there, the Java Generics FAQ. It's really worth reading.

Now I'm going to speculate a bit, in the sense that I think that you're exposing the repository to a user, but you don't want to expose the JpaUser class. But that doesn't make sense, because a repository is a very basic interface that you shouldn't expose in a library.

So, without having all the information, but by doing an educated guess, I can see two scenarios:

  1. You don't expose the repository to a user, in which case you simply handle JpaUser objects.
  2. You do want a user to use User objects, in which case you should build a façade that hides the repository.

Edit: I've quickly made a façade class that you might want to use as a starting point.

public class RepositoryFacade {

    private final UserRepository<? extends User> repository;

    public RepositoryFacade(UserRepository<? extends User> repository) {
        this.repository = repository;
    }

    public List<User> findAll(final UserCriteria userCriteria, final PageDetails pageDetails) {
        return repository.findAll(userCriteria, pageDetails)
                .stream()
                .collect(Collectors.toList());
    }

    public User findByEmail(final String email) {
        return repository.findByEmail(email);
    }
}

public RepositoryFacade getJpaUserFacade() {
    return new RepositoryFacade(new JpaUserRepository());
}

It compiles without any warnings, because the compiler infers the correct types. I'll admit that I find it lacking a certain elegance, but it works.

SeverityOne
  • 2,476
  • 12
  • 25
  • Thanks for replying. I edited my question to show the repository declaration. See EDIT2. – Sikor Apr 28 '18 at 19:10
  • Yes, I read it now. I still think that the service layer should not know about the repository, though. Can't you use Spring's dependency injection mechanism? – SeverityOne Apr 28 '18 at 19:11
  • The service layer does not know anything about repository implementation as it is in completely different module that can be swapped. It only knows the interface and I am using Spring's dependency injection but it fails when I declare the repository as `UserRepository userRepository;` – Sikor Apr 28 '18 at 19:14
  • But the service layer *is* accessing the repository. My point is that you should not expose the repository in the first place. Instead, write a façade that returns the `User` type. You can even make a generic method out of this, something that takes a `Collection extends User>` and returns a `Collection`. Then all implementations dervice from some abstract database layer class, and Bob's your uncle. – SeverityOne Apr 28 '18 at 19:20
  • Makes sense, but wouldn't that require to do the casting from `Collection extends User>` to `Collection` in the facade itself? – Sikor Apr 28 '18 at 20:20
  • I've updated my answer with a possible implementation of such a façade class. – SeverityOne Apr 28 '18 at 21:01
  • 1
    That's exactly what I thought. It basically copies a List to a new type `List`. While it solves the problem by getting rid of the warnings, it also performs additional logic. I am going to upvote your answer, because Facade is a nice way to achieve certain things, but I am wondering if my problem can be solved using generics only. Thanks a lot for your help. – Sikor Apr 28 '18 at 21:17
  • 1
    Actually, I've just realised what your problem is. You define `UserRepository` as a generic class, but you don't want to expose the generic type parameter. That doesn't make sense. So refactor `UserRepository` to get rid of the type parameter. – SeverityOne Apr 28 '18 at 22:02
  • 1
    It's somewhat true. There is a generic interface, but the class that implements said interface is not generic. I've joined this project recently and I'm still figuring out what's happening here :D. I'm probably gonna get rid of the generics in the interface and be gucci. – Sikor Apr 28 '18 at 22:43
0

The following should work just fine without exceptions:

UserRepository<user> userRepository = ...
List<User> users = userRepository.findAll(userCriteria, pageDetails);

As a side note, not related to your problem, you may want to consider defining your class as follows instead:

@Repository
public class JpaUserRepository implements UserRepository<? extends JpaUser> {
    public List<? extends JpaUser> findAll(UserCriteria userCriteria, PageDetails pageDetails) {
       //implementation
    }

    public JpaUser findByEmail(String email) {
       //implementation
    }
}

Since the List returned should be unmodifiable it gives you a bit more flexibility in your implementation. It allows subclasses to return lists which contain subclasses of JpaUser

So for example if there is a subclass called AwesomeJpaUser then a subclass can return List safely. With your current implementation this would not be allowed.

  • Unfortunately, declaring `UserRepository` causes dependency injection problems as Spring does not pick up my repository implementation which is `UserRepository` and I cannot make such declaration because JpaUser class is not visible to service layer. Thanks for a side note tip :) – Sikor Apr 28 '18 at 19:47