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 :)

Leave a Reply