Checkstyle CustomImportOrder

Fix import order in Java class using Checkstyle's CustomImportOrder module. This article explains how I did it for Nuxeo Online Services.

Overview

Today I would like to share my recent work on Checkstyle at Nuxeo. In our codebase, we use Java heavily. Managing import statements for Java files is not easy, import order might be changed unintentionally. To avoid this kind of manual checking, I decided to add a new rule for Checkstyle: CustomImportOrder.

After reading this article, you will understand:

  • The Different groups in “CustomImportOrder”
  • Modeling import statements
  • Configuring Checkstyle module
  • Fixing existing import
  • Other tasks to handle

If you don’t have time to read the whole article, here is the summary of the changes in Checkstyle configuration (checkstyle.xml):

@@ -27,6 +27,11 @@
     <module name="AvoidStarImport"/>
     <module name="RedundantImport"/>
     <module name="UnusedImports"/>
+    <module name="CustomImportOrder">
+      <property name="customImportOrderRules" value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/>
+      <property name="specialImportsRegExp" value="^org\."/>
+    </module>

     <!-- Miscellaneous -->
     <module name="ArrayTypeStyle"/>

Import Groups

According to Checkstyle documentation Checkstyle CustomImportOrder > Rule Description, the rule consists 5 groups: STATIC, SAME_PACKAGE(n), THIRD_PARTY_PACKAGE, STANDARD_JAVA_PACKAGE, and SPECIAL_IMPORTS.

STATIC group. This group sets the ordering of static imports.

SAME_PACKAGE(n) group. This group sets the ordering of the same package imports. Imports are considered on SAME_PACKAGE group if n first domains in package name and import name are identical:

package java.util.concurrent.locks;

import java.io.File;
import java.util.*; //#1
import java.util.List; //#2
import java.util.StringTokenizer; //#3
import java.util.concurrent.*; //#4
import java.util.concurrent.AbstractExecutorService; //#5
import java.util.concurrent.locks.LockSupport; //#6
import java.util.regex.Pattern; //#7
import java.util.regex.Matcher; //#8

If we have SAME_PACKAGE(3) on configuration file, imports #4-6 will be considered as a SAME_PACKAGE group (java.util.concurrent.*, java.util.concurrent.AbstractExecutorService, java.util.concurrent.locks.LockSupport). SAME_PACKAGE(2) will include #1-8. SAME_PACKAGE(4) will include only #6. SAME_PACKAGE(5) will result in no imports assigned to SAME_PACKAGE group because actual package java.util.concurrent.locks has only 4 domains.

THIRD_PARTY_PACKAGE group. This group sets ordering of third party imports. Third-party imports are all imports except STATIC, SAME_PACKAGE(n), STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS.

STANDARD_JAVA_PACKAGE group. By default, this group sets ordering of standard java/javax imports.

SPECIAL_IMPORTS group. This group may contain some imports that have particular meaning for the user.

Modeling Import Statements

Before applying the Checkstyle module CustomImportOrder, I have to understand how Nuxeo import statements work. Nuxeo import order is defined by file nuxeo.importorder:

#Organize Import Order
#Thu Dec 04 14:57:39 CET 2014
3=com
2=org
1=javax
0=java

It means Java (java) and Java EE (javax) import statements go first; then non-profitable organization (org) go next; finally, the company’s statements go last (com). It is worth to mention that static statements are not part of this order. In IntelliJ IDEA, static statements go before Java statements, so I just followed the existing rule, thus considered it as the order “-1”.

package com.nuxeo.pkg;

import static com.nuxeo.pkg.Constants.A;
import static com.nuxeo.pkg.Constants.B;
import static com.nuxeo.pkg.Constants.C;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;

import org.apache.commons.io.FileUtils;

import com.nuxeo.pkg.ClassA;
import com.nuxeo.pkg.ClassB;
import com.nuxeo.pkg.ClassC;

As you can see, static statements match Checkstyle group STATIC; Java and Java EE statements match Checkstyle group STANDARD_JAVA_PACKAGE, defined by the regular expression: ^(java|javax)\.. For the remaining ones, there are only org.* and com.*. But how to match them with groups? My first feeling is to avoid SAME_PACKAGE(n) group, because there is no distinction for packages starting with the same package name as the current one. So SAME_PACKAGE(n) is not an option. Then, I went to THIRD_PARTY_PACKAGE: since this group contains all statements not being included by other groups, it can fit our need! Make org.* goes to SPECIAL_IMPORT group, then com.* can be considered as others. Therefore, the import groups are modelized as:

  1. STATIC
  2. STANDARD_JAVA_PACKAGE
  3. SPECIAL_IMPORT
  4. THIRD_PARTY_PACKAGE

Configuring Checkstyle

Based on the result above, the Checkstyle module can be configured with property customImportOrderRules containing the 4 groups, with ### as the separator:

<property
  name="customImportOrderRules"
  value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE" />

Combined with special imports regular expression:

<property
  name="specialImportsRegExp"
  value="^org\." />

and all remaining properties can be kept as default. They don’t have to be changed.

The final diff in checkstyle.xml is:

@@ -27,6 +27,11 @@
     <module name="AvoidStarImport"/>
     <module name="RedundantImport"/>
     <module name="UnusedImports"/>
+    <module name="CustomImportOrder">
+      <property name="customImportOrderRules" value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/>
+      <property name="specialImportsRegExp" value="^org\."/>
+    </module>

     <!-- Miscellaneous -->
     <module name="ArrayTypeStyle"/>

Fix Existing Imports

Once done, I had to fix all existing import statements. I launched the following Maven command to check them manually:

$ mvn checkstyle:check

They might also be fixed using IDE auto-format tool. For example, in IntelliJ IDEA, you can select the root directory in Project view, right-click and choose either “Reformat Code” or “Optimize Imports” to do that.

Optimize Imports

Other Tasks

After the fix, I have to notify teammates about the changes, also ensure their IDE settings have the custom import order defined by our own file nuxeo.importorder. This step is very important because teammates might not aware of such changes.

Conclusion

Today, I shared my work experience about adding CustomImportOrder into existing checkstyle rules for Maven project. We saw the 5 import groups for static, same-package, third-party, standard Java, and special imports.

By the way, I sent some pull-requests to the Checkstyle project recently. You can see my contributions here: https://github.com/checkstyle/checkstyle/commits?author=mincong-h. Thanks to them, I believe I will be able to share more with you in the future, related to Checkstyle or code-quality in general. Hope you enjoy this article, see you the next time!

References