Blog post

New Spring framework rules in SonarQube

Jonathan Vila

Jonathan Vila

Developer Advocate - Java

Date

  • SonarQube
  • Spring
  • Java
  • Code Quality

Introduction

In SonarQube Server 2025 Release 2, we introduced new rules to improve code quality and enforce best practices in Spring Framework applications. These rules focus on various aspects of Spring development, including event handling, scheduling, data, MVC, caching, dependency injection, and testing. By following these rules, developers can write high-quality Spring applications.

This article provides an overview of the new code smells and bug rules, explaining their purpose, and showing both compliant and non-compliant Java code examples.

Summary of Spring features covered by the new rules

  1. Event-driven communication
  2. Task scheduling
  3. Spring MVC
  4. Spring Data
  5. Caching
  6. Dependency Injection
  7. Testing


Spring features and potential issues


Event-driven communication

In the Spring Framework, Application Events provide a way to enable communication between different components of an application in a loosely-coupled manner. These events are published and listened to within the Spring Application Context and allow components to react to certain actions or states within the application. The Spring context already publishes several events like ContextStartedEvent, ApplicationStartedEvent, or RequestHandleEvent among others.

Rule: @EventListener methods should have at most one parameter (S7185) 

Methods annotated with @EventListener listen to a particular event type. These methods should have at most one parameter, representing the event. Although the code will compile with additional parameters, Spring will fail when trying to start the context.

This SonarQube rule checks the code and helps to avoid raising an IllegalStateException during runtime.

Non-Compliant Example:

@EventListener

public void handleEvent(CustomEvent event, String extraParam) {

// Incorrect: extra parameter present

}

Compliant Example:

@EventListener

public void handleEvent(CustomEvent event) {

// Correct: only one parameter

}

Task scheduling

Spring offers a convenient library to schedule tasks. It’s easy … just a matter of using an annotation and the rate we want the task to be scheduled. 

The @Scheduled annotation marks methods that should be executed at scheduled intervals. It provides a simple and powerful way to schedule tasks without dealing with lower-level scheduling mechanisms like Timer or ScheduledExecutorService.

Rule @Scheduled Should Only Be Applied to No-Arg Methods (S7184) 

Methods annotated with @Scheduled should not have parameters because Spring does not support it. The SonarQube rule checks the code and helps avoid raising an IllegalStateException during runtime.

Non-Compliant Example:

@Scheduled(fixedRate = 5000)

public void scheduledTask(String param) {

// Incorrect: method should have no parameters

}

Compliant Example:

@Scheduled(fixedRate = 5000)

public void scheduledTask() {

// Correct: no parameters

}

Spring MVC

Rule: @InitBinder methods should have void return type (S7183) 

In Spring MVC, sometimes it’s needed to sanitize or preprocess the data coming to a controller, such as to prevent fields with only blank spaces, or to reformat dates, among other use cases.

To help with that, Spring provides an annotation for methods: @InitBinder. This annotation is used with the methods that initialize WebDataBinder and works as a preprocessor for each request coming to the controller.

By design, these methods should return void. This SonarQube rule prevents you from creating binding methods that return a value and raising an IllegalStateException on runtime.

Non-Compliant Example:

@InitBinder

public WebDataBinder initBinder(WebDataBinder binder) {

return binder; // Incorrect: should not return anything

}

Compliant Example:

@InitBinder

public void initBinder(WebDataBinder binder) {

// Correct: void return type

}

Spring Data

Rule: methods returning "Page" or "Slice" must take "Pageable" as an input parameter (S7186) 

The Spring Data Repository supports paging for queries, allowing you to return results in small, manageable chunks rather than retrieving an entire large result set.

The conventional approach to paginating data in Spring is to use the Pageable interface to control pagination and to store the query results into a Page or Slice. If a query method in a Repository returns a Page or Slice without taking a Pageable as input, it raises a runtime exception.

This rule raises an issue on queries in a Repository that return a Page or Slice without taking a Pageable as input, avoiding a runtime error.

Noncompliant code example:

interface ItemRepository extends JpaRepository<Item, Long> {

Page<Item> findItems(); //non compliant, no Pageable parameter

}

Compliant solution:

interface ItemRepository extends JpaRepository<Item, Long> {

Page<Item> findItems(Pageable pageable);

}

Caching

In Spring, the @Cache* annotations are part of the Spring Cache Abstraction and work with the cache and the method's result. Using caches can reduce the number of times a method is executed and can help optimize your application's performance, particularly for methods with expensive operations like database queries, complex calculations, or external API calls.

When a method is annotated with @Cacheable, Spring will check if the result for that method with the given parameters already exists in the cache. If it exists, Spring will return the cached value instead of executing the method again. If the result is not in the cache, Spring will execute the method and store the result in the cache for future use. 

For @CachePut, our code will introduce a new value in the cache, while for @CacheEvict the cache will be erased when the method is hit.

Rule: @Cache* Annotations Should Only Be Applied on Concrete Classes (S7180) 

Spring caching annotations like @Cacheable, @CachePut, and @CacheEvict should not be used on interfaces or interface methods in order to work. Any interface method (except default methods) annotated with @Cache* will be ignored by Spring in the caching process if you are not using the default proxy mode.

This SonarQube rule warns about using @Cache annotations in interfaces and interface methods, so they will still work if you change to a weaving-based aspect.

Non-Compliant Example:

public interface UserService {

@Cacheable("users")

User getUserById(Long id); // Incorrect: annotation on interface

}

Compliant Example:

public class UserServiceImpl implements UserService {

@Override

@Cacheable("users")

public User getUserById(Long id) {

// Correct: annotation on concrete class

}

}

Rule: @Cacheable and @CachePut should not be combined (S7179) 

@Cacheable retrieves data from the cache introduced by Spring in the first call to the method with the same signature, while @CachePut updates always the cache. Using both annotations on the same method leads to inconsistencies.

This SonarQube rule warns about @CachePut and @Cacheable annotations being used on the same method and helps to have consistency and higher readability in our code.

Non-Compliant Example:

@Cacheable("users")

@CachePut("users")

public User getUserById(Long id) {

// Incorrect: contradictory caching behavior

}

Compliant Example:

@Cacheable("users")

public User getUserById(Long id) {

// Correct: only @Cacheable used

}

Dependency Injection

Spring Framework's dependency injection (DI) is a powerful feature that simplifies the development and testing of applications. With DI, you can declare the dependencies of a class in its constructor or through annotations, and Spring will automatically wire them together at runtime. This eliminates the need for manual instantiation and wiring, making your code more modular, maintainable, and testable. Additionally, DI promotes loose coupling between components, allowing for greater flexibility and extensibility, and even defining different bean scopes (singleton, request, session, …). Overall, Spring DI helps you write more robust and more adaptable code of the highest quality.

Rule: injecting data into static fields is not supported by Spring (S7178) 

Although possible, it’s always a bad practice to inject beans directly in fields, and we should use constructor injection preferably. What’s more, Spring does not support dependency injection into static fields.

This SonarQube rule warns about using injection annotations in static fields and avoids runtime errors or unexpected results as the injection will not happen and the field will be null. 

Non-Compliant Example:

@Component

public class MyComponent {

@Autowired

private static MyService myService; // Incorrect: static field injection

}

Compliant Example:

@Component

public class MyComponent {

private final MyService myService;

@Autowired

public MyComponent(MyService myService) {

this.myService = myService;

}

}

Testing

Spring also provides a comprehensive testing framework that makes it easy to test your Spring applications. The Spring TestContext Framework provides a set of annotations and utilities that simplify the setup and execution of tests for Spring components. These annotations allow you to easily inject mocks or stubs into your tests, set up and tear down test data, and verify the state of your application after a test has run.

Among all the annotations available for Spring, we will focus on @DirtiesContext, which provides a mechanism to create a clean environment for each test, and @BeforeTransaction / @AfterTransaction, which intercepts a data transaction and executes a method at the given hook.

DirtiesContext is a powerful annotation in the Spring framework that indicates that a test method dirties the Spring application context, requiring a new context to be created for subsequent tests. This annotation is particularly useful when a test method modifies the state of shared beans or performs operations that cannot be easily rolled back.

By using DirtiesContext, developers can ensure that each test method runs in a clean and isolated environment, preventing interference from previous tests and improving the reliability and repeatability of the test suite. Additionally, DirtiesContext helps developers identify tests that modify the application context, making it easier to track down and fix potential issues.

The BeforeTransaction and AfterTransaction method annotations allow you to execute code before and after a transaction is committed or rolled back. This can be useful for setting up test data or verifying the state of the database after a transaction has been completed.

Rule: use appropriate @DirtiesContext modes (S7177) 

In a Spring application, the @DirtiesContext annotation marks the ApplicationContext as dirty and indicates that it should be cleared and recreated. This is important in tests that modify the context, such as altering the state of singleton beans or databases.

Misconfiguring @DirtiesContext by setting the methodMode argument at the class level or the classMode argument at the method level will make the annotation have no effect.

This rule will raise an issue when the incorrect mode is configured on a @DirtiesContext annotation.

Non-Compliant Example:

@ContextConfiguration

@DirtiesContext(methodMode = MethodMode.AFTER_METHOD) // Noncompliant, for class-level control, use classMode instead.

public class TestClass {

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) // Non compliant, for method-level control use methodMode instead

public void test() {...}

}

Compliant Example:

@ContextConfiguration

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)

public class TestClass {

@DirtiesContext(methodMode = MethodMode.AFTER_METHOD)

public void test() {...}

}

Rule: methods annotated with "@BeforeTransaction" or "@AfterTransaction" must respect the contract (S7190) 

In tests configured with Spring’s @Transactional annotation, methods annotated with @BeforeTransaction or @AfterTransaction must be void and have no arguments.

This SonarQube rule will check code that deviates from this contract by having a non-void return type or accepting arguments and keeps Spring from throwing a runtime error.

Non-compliant code

@Transactional

public class TransactionalTest {

@BeforeTransaction

public String setupTransaction(int x) { // non-compliant, method should be void and have no argument

// Setup logic

}

@AfterTransaction

public int cleanupTransaction(int x) { // non-compliant, method should be void and have no argument

// Cleanup logic

}

}

Compliant code

@Transactional

public class TransactionalTest {

@BeforeTransaction

public void setupTransaction() {

// Setup logic

}

@AfterTransaction

public void cleanupTransaction() {

// Cleanup logic

}

}

Conclusion

The Spring framework offers a huge list of benefits to developers. Its different modules simplify the development and testing process, which can be achieved by using annotations. But while Spring can significantly enhance an application's performance, it is essential to use the library correctly. Proper usage will increase code readability and consistency and help avoid unexpected runtime errors. By following best practices and leveraging Spring's features effectively, developers can harness the full potential of the framework and create robust, high-performing applications.

SonarQube provides a series of rules that help developers ensure their Spring applications are structured correctly, improving maintainability and reliability. Adopting these best practices will lead to higher quality and more efficient codebases.


Get the latest SonarQube Server to start finding and fixing these issues with Spring in your code now! 

Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

I do not wish to receive promotional emails about upcoming SonarQube updates, new releases, news and events.

By submitting this form, you agree to the storing and processing of your personal data as described in the Privacy Policy and Cookie Policy. You can withdraw your consent by unsubscribing at any time.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.