How to format your project’s Java code with git hooks and finally get rid of annoying IDE specifics

In most of our projects, every developer can use the IDE of their choice which quickly leads to quite some issues with code formatting. Therefore, we needed a solution to automatically format the code before a commit is pushed to the central git repository. For JavaScript we already implemented that a long time ago and the feedback was great. There’s no more discussion about formatting in code reviews (some exceptions if a //doNotFormat comment is used) anymore, it’s not necessary to create checks on CI level etc. We also added some linting that prevents the commit if there’s a violation detected and even the critics started to like this approach :)
Now, it was time to have a look at some non-JavaScript: Some Java using maven…

Solution

Formatter

At first, we looked for a maven plugin that is able to format the files with a standard Eclipse formatter configuration. We decided for spotless for the time being:

...
<spotless.include></spotless.include>
...
<plugin>
   <groupId>com.diffplug.spotless</groupId>
   <artifactId>spotless-maven-plugin</artifactId>
   <version>1.15.0</version>
   <configuration>
      <java>
         <excludes>
            <!-- excluding some auto generated files -->
            <exclude>com/client/something/*.java</exclude>
         </excludes>
         <includes>
            <include>${spotless.include}</include>
         </includes>
         <eclipse>
            <file>${basedir}/java-formatter-config.xml</file>
         </eclipse>
         <encoding>UTF-8</encoding>
         <removeUnusedImports/>
      </java>
   </configuration>
</plugin>
...
  • java-formatter-config.xml is an Eclipse formatter configuration and can also be imported into most IDEs.
  • spotless.include is a glob pattern that defines which files should be formatted. Since we only want to format the files touched in a commit, it’s necessary to define these precisely when calling mvn spotless:apply.
  • One downside of spotless is its performance. Currently, it runs for about 3-4 seconds, so we might switch to something else at some point in the future. For the time being, it’s okay.

Calling it as a pre-commit hook

Our key requirement is that it shouldn’t be necessary to set up the git hooks manually for each developer. In the JavaScript parts, we’re using lint-staged with husky to achieve this, which is working fine for us. Thanks to a colleague, I was made aware of frontend-maven-plugin, which is capable of installing node+npm and execute a npm install afterwards. So we added the following pluing to our pom.xml:

...
<plugin>
   <groupId>com.github.eirslett</groupId>
   <artifactId>frontend-maven-plugin</artifactId>
   <version>1.6</version>
   <executions>
      <execution>
         <id>install node and npm</id>
         <goals>
            <goal>install-node-and-npm</goal>
         </goals>
      </execution>
      <execution>
         <id>npm install</id>
         <goals>
            <goal>npm</goal>
         </goals>
      </execution>
   </executions>
   <configuration>
      <nodeVersion>v10.12.0</nodeVersion>
   </configuration>
</plugin>
...

So once a user calls mvn generate-source, node+npm will be installed locally into the project. To install lint-staged and husky and configure them, we added a minimal package.json:

{
  "name": "ClientProject",
  "private": true,
  "version": "1.0.0",
  "description": "ClientProject",
  "scripts": {
    "precommit": "lint-staged"
  },
  "author": "",
  "license": "UNLICENSED",
  "devDependencies": {
    "husky": "0.14.3",
    "lint-staged": "7.1.3"
  },
  "lint-staged": {
    "linters": {
      "*.java": [
        "./formatter.sh",
        "git add"
      ]
    }
  }
}

This defines that lint-staged is called as a precommit hook, which checks if .java files were changed. If yes, it passes the changed files as a parameter to the formatter.sh as well as the git add afterwards. The workaround through the formatter.sh is required, as the plugin passes the full path of the files as arguments, while spotless requires the files to be formatted as a single comma-separated string relative to the location of the project root.

formatter.sh:

#!/bin/sh

function join_by {
  local IFS="$1";
  shift;
  echo "$*";
}

PROJECT_DIR="$(pwd)"
FILES=$(join_by "," "$@")

mvn spotless:apply -f $PROJECT_DIR/pom.xml -Dspotless.include=${FILES//$PROJECT_DIR/}

Workarounds

In case you don’t want your commit to be formatted, just run git commit with –no-verify, or disable the “run git hooks” in the respective IntelliJ pop-up. But be aware that you might be approached by the other developers ;)

Conclusion

Now, when committing any .java file, it is automatically formatted according to rules in java-formatter-config.xml without the user having to take care of this.

By the way, if you want to be notified for new posts, just sign up with your email address on the right and/or like my Facebook-Page :)

FindBugs & Reject.if* False Positives

In my current project we’re using Reject.if* (ifNull, ifNotNull, ifTrue…) methods for several validations, error management etc. As we’re also using FindBugs, we stumbled upon a problem with code like the following, in which FindBugs comes up with a NP_NULL_ON_SOME_PATH warning, in this case a false positive:

void foo(String bar) {
    Reject.ifNull(bar);
    bar.getBytes();
}

After searching for a solution and asking some people, we finally found a solution that works pretty well in this case: Passing a system property called findbugs.assertionmethods [1] through Maven to FindBugs. The code for it in the pom looks somewhat like this:

...
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>findbugs-maven-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <effort>Default</effort>
        <threshold>Medium</threshold>
        <xmlOutput>true</xmlOutput>
        <findbugsXmlOutputWithMessages>true</findbugsXmlOutputWithMessages>
        <omitVisitors>FindReturnRef</omitVisitors>
        <jvmArgs>-Dfindbugs.assertionmethods=de.mopri.utils.Reject.ifNull</jvmArgs>
    </configuration>
</plugin>
...

By using the jvmArgs element the system property is properly passed down to FindBugs and telling it that the method Reject.ifNull is used as an assertion. Now FindBugs acknowledges that bar.getBytes() won’t throw a NullPointerException :)

Control flow with exceptions :D

Reminds me about some discussions I had about controlling your program flow with exceptions :mrgreen:

Using exceptions to control program flow is a bad idea. Creating exceptions requires taking a snapshot of the call stack, and that’s costly. – John Flinchbaugh Oct 16 ’08 at 12:17

Not if you override the method that generates the call stack in the exception you throw to do nothing. That’s the advantage of a custom exception. – shemnon Oct 17 ’08 at 14:03

The whole thread can be found here on stackoverflow.