This is an issue when a framework annotation or metadata marker is used alongside another annotation that already includes it through annotation inheritance or composition mechanisms.
Some frameworks use composed annotations or attribute inheritance to build functionality. A composed annotation is one that is declared to include another annotation’s behavior. When you use an annotation that composes other annotations, you automatically get all the functionality of those included annotations.
For example, a framework might provide a combined controller annotation that is composed from a basic controller marker and a response formatting marker. This means when you add the combined annotation to a class, the framework automatically treats it as if it also has both the basic controller marker and the response formatting marker.
When developers explicitly add both annotations, the parent annotation becomes redundant. It has no effect on how the framework processes the class, but it creates several problems:
The most common cases involve framework stereotype annotations. A base component marker is often the foundation, with the framework providing specialized versions like service markers, data access markers, controller markers, and configuration markers. Each of these specialized annotations is composed from the base component marker, so adding the base marker explicitly is redundant.
Similar redundancies occur with combined controller annotations (which include both basic controller and response formatting markers), application bootstrap annotations (which include configuration, auto-configuration, and component scanning markers), and various framework test annotations.
In Spring Framework, this specifically applies to meta-annotations. Common examples include:
This impacts:
Remove the redundant parent annotation. Keep only the most specific annotation that provides the functionality you need.
For stereotype annotations (@Service, @Repository, @Controller, @Configuration), remove
@Component since it’s already included in these specialized annotations.
@Component // Noncompliant
@Service
public class UserService {
public User findById(Long id) {
// service logic
}
}
@Service
public class UserService {
public User findById(Long id) {
// service logic
}
}
For REST controllers, remove @Controller and class-level @ResponseBody since @RestController already
includes both. Also remove method-level @ResponseBody within @RestController classes since it’s already applied to all
methods.
@Controller // Noncompliant
@RestController
public class UserController {
@ResponseBody // Noncompliant
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
@RestController
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
For Spring Boot applications, remove @Configuration, @SpringBootConfiguration, @EnableAutoConfiguration, and
@ComponentScan (when used without custom attributes) since @SpringBootApplication already includes all of these.
@Configuration // Noncompliant
@EnableAutoConfiguration // Noncompliant
@ComponentScan // Noncompliant
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
For Spring test classes, remove @ExtendWith(SpringExtension.class) when using specialized test annotations like
@SpringBootTest, @WebMvcTest, @DataJpaTest, or @WebFluxTest since they already include this
extension. Also remove @Transactional when using @DataJpaTest since it’s already included.
@ExtendWith(SpringExtension.class) // Noncompliant
@SpringBootTest
class UserServiceIntegrationTest {
@Test
void testUserCreation() {
// test logic
}
}
@SpringBootTest
class UserServiceIntegrationTest {
@Test
void testUserCreation() {
// test logic
}
}