1. Introduction
As software developers, weβre always looking for the best practices for using a given technology or library. Naturally, there are debates sometimes.
One such debate is regarding the placement of Springβs @Service annotation. Since Spring provides alternative ways to define beans, itβs worth paying attention to the whereabouts of stereotype annotations.
In this tutorial, weβll look at the @Service annotation and examine whether it works best to place it on interfaces, abstract classes, or concrete classes.
2. @Service on Interfaces
Some developers may decide to put @Service on interfaces because they want to:
- Explicitly show that an interface should only be used for service-level purposes
- Define new service implementations and have them automatically detected as Spring beans during startup
Letβs see how it looks if we annotate an interface:
@Service
public interface AuthenticationService {
boolean authenticate(String username, String password);
}
As we notice, AuthenticationService becomes more self-descriptive now. The @Service mark advises developers to use it only for the business layer services and not for the data access layer or any other layers.
Normally, thatβs fine, but thereβs a drawback. By putting Springβs @Service on interfaces, we create an extra dependency and couple our interfaces with an outside library.
Next, to test the autodetection of our new service beans, letβs create an implementation of our AuthenticationService:
public class InMemoryAuthenticationService implements AuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}
We should pay attention that our new implementation, InMemoryAuthenticationService, doesnβt have the @Service annotation on it. We left @Service only on the interface, AuthenticationService.
So, letβs run our Spring context with the help of a basic Spring Boot setup:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AuthenticationService authService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
When we run our app, we get the infamous NoSuchBeanDefinitionException, and the Spring context fails to start:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.baeldung.annotations.service.interfaces.AuthenticationService' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
...
Therefore, placing @Service on interfaces isnβt enough for the auto-detection of Spring components.
3. @Service on Abstract Classes
Using the @Service annotation on abstract classes isnβt common.
Letβs test it out to see if it achieves our objective of causing Spring to autodetect our implementation classes.
Weβll start by defining an abstract class from scratch and putting the @Service annotation on it:
@Service
public abstract class AbstractAuthenticationService {
public boolean authenticate(String username, String password) {
return false;
}
}
Next, we extend AbstractAuthenticationService to create a concrete implementation without annotating it:
public class LdapAuthenticationService extends AbstractAuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}
Accordingly, we also update our AuthApplication, to inject the new service class:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AbstractAuthenticationService authService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
We should notice that we donβt try to inject the abstract class directly here, which is not possible. Instead, we intend to acquire an instance of the concrete class LdapAuthenticationService, depending only on the abstract type. This is a good practice, as the Liskov Substitution Principle also suggests.
So, we run our AuthApplication, again:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.baeldung.annotations.service.abstracts.AbstractAuthenticationService' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:
...
As we can see, the Spring context doesnβt start. It ends up with the same NoSuchBeanDefinitionException exception.
Certainly, using @Service annotation on abstract classes doesnβt have any effect in Spring.
4. @Service on Concrete Classes
Contrary to what weβve seen above, itβs quite a common practice to annotate the implementation classes instead of abstract classes or interfaces.
In this way, our goal is mostly to tell Spring this class is going to be a @Component and mark it with a special stereotype, which is @Service in our case.
Therefore, Spring will autodetect those classes from the classpath and automatically define them as managed beans.
So, letβs put @Service on our concrete service classes this time around. Weβll have one class that implements our interface and a second that extends the abstract class that we defined previously:
@Service
public class InMemoryAuthenticationService implements AuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}
@Service
public class LdapAuthenticationService extends AbstractAuthenticationService {
@Override
public boolean authenticate(String username, String password) {
//...
}
}
We should take notice here that our AbstractAuthenticationService doesnβt implement the AuthenticationService here. Hence, we can test them independently.
Finally, we add both of our service classes into the AuthApplication and give it a try:
@SpringBootApplication
public class AuthApplication {
@Autowired
private AuthenticationService inMemoryAuthService;
@Autowired
private AbstractAuthenticationService ldapAuthService;
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
Our final test gives us a successful result, and the Spring context boots up with no exceptions. Both of the services are automatically registered as beans.
5. The Result
Eventually, we saw the only working way is putting @Service on our implementation classes to make them auto-detectable. Springβs component scanning doesnβt pick up the classes unless they are annotated separately, even theyβre derived from another @Service annotated interface or abstract class.
Plus, Springβs documentation also states that using @Service on implementation classes allows them to be autodetected by the component scan.
6. Conclusion
In this article, we examined different places of using Springβs @Service annotation and learned where to keep @Service to define service-level Spring beans so that theyβll be autodetected during component scanning.
Specifically, we saw that placing the @Service annotation on interfaces or abstract classes has no effect and that only concrete classes will be picked up by component scanning when theyβre annotated with @Service.
