Skip to Content
Jora K

4 mins read


Prevent annotations like @Transactional on private methods with Checkstyle

Custom Checkstyle rule to block private methods with annotations in Java. How to prevent @Transactional on private methods in Java using Checkstyle? Why do Spring AOP annotations not work on private methods and how to fix it?


Intro

Learn how to use a custom Checkstyle rule to prevent using annotations (like @Transactional) on private methods.

Motivation

In Java, annotations like @Transactional are often used to manage transactions declaratively. However, applying such annotations to private methods can lead to unexpected behavior, as the proxy mechanism used by frameworks like Spring may not intercept calls to private methods. This can result in transactions not being applied as intended, leading to potential data inconsistencies and bugs that are hard to trace.

Errors like this can be hard to detect because there is no compilation error or warning. The application may run without issues in many cases, but when a private method with @Transactional is invoked, the expected transactional behavior will not occur, leading to subtle bugs that can be difficult to diagnose.

To prevent such issues, use this custom Checkstyle rule to highlight usage of @Transactional on private methods during code style checks.

Problem Statement

There are many Java annotations that rely on Aspect-Oriented Programming (AOP) through dynamic proxies and will not work on private methods for the same reasons as @Transactional.

For example: @Cacheable, @CachePut, @CacheEvict, @Async, @PreAuthorize, @PostAuthorize, @Secured.

Why these annotations fail on private methods

  • Proxy-based mechanism: Spring's default AOP implementation creates proxies that wrap your beans. These proxies can only intercept public method calls that come from outside the object.
  • Self-invocation: When a method inside a bean calls another method within the same bean (e.g., this.myPrivateMethod()), the call is direct. It does not go through the proxy, and therefore, no AOP advice (like caching, retrying, or security) is applied.
  • Silent failure: Just like with @Transactional, annotating a private method with one of these AOP annotations won't result in an error. This makes it difficult to detect, as the code will simply behave differently than expected, potentially leading to hard-to-debug issues.

Checkstyle-no-private-method-annotation-rule

Installation

To use this custom Checkstyle rule, you need to include the checkstyle-no-private-method-annotation-rule library in your project.

In Maven Checkstyle plugin configuration, add this dependency:

...
<dependency>
    <groupId>dev.jora</groupId>
    <artifactId>checkstyle-no-private-method-annotation-rule</artifactId>
    <version>1.0.1</version>
</dependency>
...

Or Gradle:

plugins {
    // ...
    id 'checkstyle'
    // ...
}
 
dependencies {
    // Add dependencies to Checkstyle plugin context
 
    // Add Checkstyle tools
    checkstyle "com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}"
    // Add new custom rule
    checkstyle 'dev.jora.checkstyle:checkstyle-no-private-method-annotation-rule:1.0.0'
}
 
checkstyle {
    // Your Checkstyle configuration here
}

Usage

  1. Update your Checkstyle configuration (XML):

    Add the custom rule to your checkstyle.xml:

    <module name="Checker">
      ...
      <module name="TreeWalker">
        ...
        <module name="NoPrivateAnnotatedMethodCheck">
             <property name="severity" value="error"/>
             <property name="forbiddenAnnotations" value="MyAnnotation"/>
             <property name="forbiddenAnnotations" value="MyAnnotation2"/>
             <property name="forbiddenAnnotations" value="Transactional"/>
        </module>
      </module>
      ...
    </module>
  2. Run Checkstyle:

    With Gradle:

    gradle checkstyleMain

Example

Violated code example:

public class Example {
    @Transactional // <-- This will trigger a Checkstyle violation
    private void doSomething() { 
    }
 
    public void validMethod() {
    }
}

Log output example:

[ERROR] Example.java:2:5: Annotation 'Transactional' is not allowed on private methods [NoPrivateAnnotatedMethod]

Conclusion

All sources are available at github.com/dyadyaJora/checkstyle-no-private-method-annotation-rule. If you find it useful, please star ⭐ this repository and follow me on Github to be notified about new lessons and content.

PROFIT!