I've had this same conversation with coworkers. I feel like this question is a little beyond my Java knowledge, but here's a shot:
I think there are 2 reasons people do it:
1. Java dynamic proxying
The way Spring honors some of the annotations like @Transaction is to create a proxy for the class.
According to the docs, Java dynamic proxying is default proxy-generation method over CGLIB-based proxies. Java dynamic proxying can only work with interfaces, so people got into the habit of creating interfaces for every services. Why is Java dynamic proxying preferred? I don't know, tbh, but these are some points:
- CGLIB used to be a separate library you had to include. Nowadays it's included with Spring
- CGLIB is a 3rd-party library to "hack" bytecode, vs the JDK-included dynamic proxying
- I guess at some point constructors were invoked twice when using CGLIB? Maybe there are other quirks?
- I have only a tiny bit of experience here, but I know CGLIB can give some opaque error messages sometimes.
I don't have enough experience to recommend going full-CGLIB just for the sake of removing redundant interfaces.
2. "Software engineering reasons", encouraged by the docs
To quote the docs:
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces.
Here the docs sort of indirectly encourage people to use interfaces. This seems to me to be a little heavy-handed, but it may be part of why it has become common practice. And once it's common practice, it becomes habit.
Conclusion
While I disagree with the idea that all service classes with a single implementation need a redundant interface, I agree that service classes should be treated as a "facade-point" in the code, encapsulating the underlying layers and providing a clean abstraction to the view layer. But I don't see why they need to have literal java interfaces unless the need arises.
That's what I think from a structural point of view. But without more CGLIB vs JDK-proxy info, I'll probably stick with the defaults.