6 min read

Setup CheckStyle in a Java project

The missing part of the documentation

Photo by Andrew Neel on Unsplash

I am an enthusiast about clean code and architecture. Together with testing and best practices I believe they build good and robust software. I really like to use static analysis tools to enforce code guidelines.

I recently started working on a Java project where we decided to introduce a static analysis tool to enforce different rules. Additionally (and most importantly), we wanted to enforce some specific architectural rules. We specifically wanted to forbid in a package to import anything outside of it within the same project codebase. I was the one in carrying out the implementation, so I thought:

Easy! I will just write a Lint rule!

As it happens sometimes, it was not as easy as I thought.

In this post I will show how I have setup CheckStyle in a Java project, the problems I had with missing documentation and what worked for me.


What is Static Code Analysis?

Before jumping into the how, let us talk about Static Code Analysis. What is it?

Static Code Analysis is a method to analyze source code without executing it. It is done by analyzing the code against a set of predefined rules. Nowadays, IDEs even have some of these tools integrated. By introducing static analysis tools, they provide immediate feedback during the development phase. The earlier we find issues in the lifecycle of development the better as it reduces the cost and effort needed to apply the necessary changes to comply with the rules.

Apart from the benefits in code quality, another big benefit of using static anaylsis tools is that they reduce the noise in code reviews. It makes code reviews less tedious. There are fewer issues or irrelevant things to be mentioned.

Such tools can also be used to enforce styling guidelines. Styling changes are a tremendous source of noise and getting rid of them has a big impact when reviewing someone else’s changes. Also, manually checking that agreed code guidelines are completely fulfilled is almost impossible to guarantee.

I particularly appreciate having these kind of rules when I do code reviews 😁.


Static analysis tools

Lint is a static code analysis tool used to find errors and bugs among other things. Since the aforementioned project is built using Maven, I started doing some Googling. I am used to using the Lint library provided by Google for Android, so I thought there may be something similar for Java projects.

After some research, I came to the conclusion that CheckStyle was a widely used tool and that it is very similar to the Lint tool I am familiar with. So let’s go with it! 🚀


Configuring the dependencies

In order to use CheckStyle in our project, we need to include it as an external dependency. If you wonder about its license, CheckStyle has a free software license: GNU Lesser General Public License (LGPL)

First of all, we need to define the dependencies in our pom.xml:

<project>
    <properties>
        <checkstyle-maven-plugin.version>3.1.2</checkstyle-maven-plugin.version>
        <checkstyle.version>8.41</checkstyle.version>
        <checkstyle.config.location>config/checkstyle/config.xml</checkstyle.config.location>
        <checkstyle.suppressions.location>config/checkstyle/suppressions.xml</checkstyle.suppressions.location>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>${checkstyle-maven-plugin.version}</version>
                <configuration>
                    <consoleOutput>true</consoleOutput>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>com.puppycrawl.tools</groupId>
                        <artifactId>checkstyle</artifactId>
                        <version>${checkstyle.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <id>validate</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.9.1</version>
            </plugin>
        </plugins>
    </build>

    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>${checkstyle-maven-plugin.version}</version>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>checkstyle</report>
                        </reports>
                        <configuration>
                            <includeTestSourceDirectory>true</includeTestSourceDirectory>
                        </configuration>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>
</project>

Here there are a few things happening:

  1. We define the plugins maven-checkstyle-plugin and maven-site-plugin.
    1. Inside of the plugin maven-checkstyle-plugin we also define checkstyle as dependency.
    2. We run CheckStyle in the validate phase.
  2. We declare the plugin maven-checkstyle-plugin in the reporting section.

Properties

Because we have the maven-checkstyle-plugin declared in two separated places, we define a property checkstyle-maven-plugin.version that holds the version number and we use it below when declaring the plugin:

<properties>
    <checkstyle-maven-plugin.version>3.1.2</checkstyle-maven-plugin.version>
</properties>

Since we declared a property for the maven-checkstyle-plugin version, why not doing the same for its checkstyle dependency? So we also declare a checkstyle.version property with the version of the checkstyle dependency:

<properties>
    <checkstyle.version>8.41</checkstyle.version>
</properties>

As shown in the pom.xml snippet, we can reference the properties like this: ${checkstyle-maven-plugin.version}.


Setup location of configuration files

Configuration file

Interestingly, the documentation of CheckStyle’s plugin says that we can configure the location of the configuration file in a configuration section in the plugin declaration, but that did not work for me for some reason. What worked was to use the project’s parameter checkstyle.config.location. So I declared a parameter with that name and I put there the path to the configuration file, relative to the project root’s directory:

<properties>
    <checkstyle.config.location>config/checkstyle/config.xml</checkstyle.config.location>
</properties>

Note: Usually, there are two configurations (Google and Sun) embedded already within the plugin and the sun_checks.xml configuration will be used by default. My approach does not use any embedded configuration and instead defines just a few rules. If we want to use one of the default configurations and we also want to add some custom rules, we may have to copy the default configuration into our own file and use that as a single source of truth for configuring CheckStyle.

Suppressions file

The suppresions file is where we declare some exceptions where we do not want CheckStyle to report any violations. My suppressions file is currently empty, but I have configured it so I have it in place in case I need it. Let us be pragmatic! It would be annoying to set it up only when we need it since we would then have to dive into the documentation again. So I would rather just set it up now and have it empty.

The suppressions file is supposed to be declared similarly as the configuration file but that also did not work, so I declared it the same way using the property checkstyle.suppressions.location:

<properties>
    <checkstyle.suppressions.location>config/checkstyle/suppressions.xml</checkstyle.suppressions.location>
</properties>

Import control file

The import-control file is where we define our import rules. Its location is defined within the configuration file. Below we will see it more in detail.


Configure CheckStyle

Configuration file

Below we can see an example of how I have set up my CheckStyle configuration.

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <property name="charset" value="UTF-8"/>

    <property name="severity" value="error"/>

    <property name="fileExtensions" value="java, properties, xml"/>

    <!-- Excludes all 'module-info.java' files              -->
    <!-- See https://checkstyle.org/config_filefilters.html -->
    <module name="BeforeExecutionExclusionFileFilter">
        <property name="fileNamePattern" value="module\-info\.java$"/>
    </module>

    <!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
    <module name="SuppressionFilter">
        <property name="file" value="config/checkstyle/suppressions.xml"/>
        <property name="optional" value="false"/>
    </module>

    <!-- Java Files -->
    <module name="TreeWalker">
        <!-- Imports -->
        <module name="ImportControl">
            <property name="severity" value="error"/>
            <property name="file" value="config/checkstyle/import-control.xml"/>
        </module>
        <module name="AvoidStarImport"/>
        <module name="RedundantImport"/>
        <module name="UnusedImports"/>

        <!-- Modifiers -->
        <module name="ModifierOrder"/>
        <module name="RedundantModifier"/>

        <!-- Long constants are defined with an uppercase ell. That is 'L' and not 'l'. -->
        <module name="UpperEll"/>

    </module>

</module>

Let us go step by step explaining everything that we are doing here:

  1. First of all we declare the Checker module for checking our files.
  2. We consider all our files to be UTF-8 encoded.
  3. We declare the severity of all the findings as error.
  4. We want to analyze all java, properties and xml files.
  5. We exclude all module-info.java files.
  6. We setup the suppressions file as mandatory.
  7. The TreeWalker analyzes the java files:
    1. We define our import-control set of rules and set their severity to error.
    2. We add rules for avoiding star imports, redundant imports and unused imports.
    3. We add rules for modifier’s order and redundant modifiers.
    4. We add a rule for long constants so they are defined with an uppercase ell. That is L and not l.

I took most of this configuration from here.

Import-control file

Below we can see an example of how I have set up my import control configuration.

<?xml version="1.0"?>
<!DOCTYPE import-control PUBLIC
        "-//Puppy Crawl//DTD Import Control 1.4//EN"
        "http://checkstyle.sourceforge.net/dtds/import_control_1_4.dtd">

<import-control pkg="org.example" strategyOnMismatch="allowed">

    <subpackage name="base">
        <allow pkg="org\.example\.base\.[^.]+" regex="true"/>
        <disallow pkg="org\.example\.[^.]+" regex="true"/>

        <subpackage name="data">
            <allow pkg="org\.example\.base\.(data|domain).*" regex="true"/>
            <disallow pkg="org\.example\.base\.presentation.*" regex="true"/>
        </subpackage>

        <subpackage name="presentation">
            <allow pkg="org\.example\.base\.(domain|presentation).*" regex="true"/>
            <disallow pkg="org\.example\.base\.data.*" regex="true"/>
        </subpackage>

        <subpackage name="domain">
            <allow pkg="org\.example\.base\.domain.*" regex="true"/>
            <disallow pkg="org\.example\.base\.(data|presentation).*" regex="true"/>
        </subpackage>
    </subpackage>

</import-control>

In the above code we have a simple setup for Clean Architecture as an example. Such configuration aims to enforce Clean Architecture’s separation of layers in the base package. Usually such separation occurs at root level, but for the purpose of this example I decided to nest it inside the base package to display how the configuration looks like for nested subpackages. In this example there are three layers: domain, data and presentation.

Something worth noting is that CheckStyle will start searching for matching rules from the deepest package level and from top to bottom of the file. This means that once a rule is met in a subpackage, it will skip the rest of the rules from parent packages. Similarly, following the order within a subpackage, once a rule is met, it will skip the rest of the rules below.

For meeting Clean Architecture’s rules, we defined the following rules:

  1. In base package:
    1. Allow all imports from org.example.base package.
    2. Disallow all imports from org.example.* package. It looks contradictory with the previous rule, but we should remember that this rule is omitted once the previous is met.
  2. In base.data package:
    1. Allow all imports from org.example.base.data and org.example.base.domain packages.
    2. Disallow all imports from org.example.base.presentation.
  3. In base.presentation package:
    1. Allow all imports from org.example.base.presentation and org.example.base.domain packages.
    2. Disallow all imports from org.example.base.data.
  4. In base.domain package:
    1. Allow all imports from org.example.base.domain.
    2. Disallow all imports from org.example.base.data and org.example.base.presentation packages.

Suppressions file

We can find a sample of the suppressions file along with more information in the documentation.


IntelliJ plugin

Having CheckStyle run automatically when we build our project is very handy, it will warn us early if we violate any rules. However, it needs a manual task to run. This is where the CheckStyle-IDEA plugin for IntelliJ comes in handy. The plugin will display warnings in the IDE for rule violations without the need to run the analysis manually.

We can configure it to use our CheckStyle configuration file to enforce our rules. For that we need to go in IntelliJ as of version 2021.1 to: Settings > Tools > CheckStyle. There we can configure the plugin to use the same CheckStyle version and the path of our configuration file.

This configuration will be stored in .idea/checkstyle-idea.xml and we can add it to our GIT changeset. Additionally, I would recommend to ignore the folder .idea/checkstyleidea-libs in the .gitignore file as this is where some libraries are copied by the plugin when performing some analysis.

Although I have focused on the IntelliJ plugin, there are similar plugins for other IDEs like VS Code too.


Summary

In this post we learned how to setup CheckStyle in our Java project, we have setup a basic configuration and we have configured the CheckStyle-IDEA plugin for IntelliJ.

Thanks for reading!

References

If you found this article interesting, share it!


Follow my journey

Get the latest updates in your inbox 📨

    No spam. Unsubscribe as your heart desires.