Spring Top

Get started with Spring 5 and Spring Boot 2, through the reference Learn Spring course:

>> LEARN SPRING

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.

As always, all the code samples and more are available over on GitHub.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Comments are closed on this article!