Linting Java Projects

This page will discuss common topics around enforcing the code quality of Java codebases using the Mill build tool

Linting with ErrorProne

When adding the ErrorPromeModule to your JavaModule, the error-prone compiler plugin automatically detects various kind of programming errors.

build.mill (download, browse)
package build
import mill._, javalib._, errorprone._


object `package` extends RootModule with JavaModule with ErrorProneModule {
  def errorProneOptions = Seq("-XepAllErrorsAsWarnings")
}
src/example/ShortSet.java (browse)
package example;

import java.util.HashSet;
import java.util.Set;

public class ShortSet {
  public static void main (String[] args) {
    Set<Short> s = new HashSet<>();
    for (short i = 0; i < 100; i++) {
      s.add(i);
      s.remove(i - 1);
    }
    System.out.println(s.size());
  }
}
> ./mill show errorProneOptions
[
  "-XepAllErrorsAsWarnings"
]

> ./mill compile
[warn] .../src/example/ShortSet.java:11:15:  [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
[warn]       s.remove(i - 1);
[warn]               ^    (see https://errorprone.info/bugpattern/CollectionIncompatibleType)
[warn] 1 warning
[warn]               ^

Error Prone augments the Java compiler’s type checker and detect common mistakes at compile time.

You just need to mix the ErrorProneModule into your JavaModule and it will automatically run with every compilation.

build.mill.scala: Enable ErrorProne in a module
package build
import mill._, scalalib._

import $ivy.`com.lihaoyi::mill-contrib-errorprone:`
import mill.contrib.errorprone.ErrorProneModule

object foo extends JavaModule with ErrorProneModule {
}

Configuration

The following configuration options exist:

def errorProneVersion: T[String]

The error-prone version to use. Defaults to , the version used to build and test the module. Find the latest at mvnrepository.com

def errorProneOptions: T[Seq[String]]

Options directly given to the error-prone processor. Those are documented as "flags" at https://errorprone.info/docs/flags

Linting with Checkstyle

build.mill (download, browse)
package build
import mill._, javalib._, checkstyle._

object `package` extends RootModule with CheckstyleModule {
  def checkstyleVersion = "9.3"
}
> ./mill checkstyle # run checkstyle to produce a report, defaults to warning without error
...src/InputWhitespaceCharacters.java:3:23: Line contains a tab character...
...src/InputWhitespaceCharacters.java:16:3: Line contains a tab character...
...src/InputFileName1.java:2:1: Top-level class MyAnnotation1 has to reside in its own source file...
...src/InputFileName1.java:13:1: Top-level class Enum1 has to reside in its own source file...
...src/InputFileName1.java:26:1: Top-level class TestRequireThisEnum has to reside in its own source file...
Audit done.

> sed -i.bak 's/warning/error/g' checkstyle-config.xml # make checkstyle error on violations

> ./mill checkstyle
error: ...src/InputWhitespaceCharacters.java:3:23: Line contains a tab character...
...src/InputWhitespaceCharacters.java:16:3: Line contains a tab character...
...src/InputFileName1.java:2:1: Top-level class MyAnnotation1 has to reside in its own source file...
...src/InputFileName1.java:13:1: Top-level class Enum1 has to reside in its own source file...
...src/InputFileName1.java:26:1: Top-level class TestRequireThisEnum has to reside in its own source file...
Audit done.

> sed -i.bak 's/\t/    /g' src/InputWhitespaceCharacters.java

> rm src/InputFileName1.java

> ./mill checkstyle # after fixing the violations, checkstyle no longer errors
Audit done.

CheckstyleModule Performs quality checks on Java source files using Checkstyle and generates reports from these checks.

To use this plugin in a Java/Scala module,

  1. Extend mill.contrib.checkstyle.CheckstyleModule.

  2. Define a configuration file checkstyle-config.xml.

  3. Run the checkstyle command.

    • flags

// if an exception should be raised when violations are found
checkstyle --check

// if Checkstyle output report should be written to System.out
checkstyle --stdout
  • sources (optional)

// incorrect paths will cause a command failure
// checkstyle a/b

// you can specify paths relative to millSourcePath
checkstyle src/a/b

// process a single file
checkstyle src/a/B.java

// process multiple sources
checkstyle src/a/b src/c/d src/e/F.java

// process with flags
checkstyle --check --stdout src/a/b src/c/d

// process all module sources
checkstyle

Shared configuration

To share checkstyle-config.xml across modules, adapt the following example.

import mill._
import mill.contrib.checkstyle.CheckstyleModule
import mill.scalalib._

object foo extends Module {

  object bar extends MyModule
  object baz extends Module {
    object fizz extends MyModule
    object buzz extends MyModule
  }

  trait MyModule extends JavaModule with CheckstyleModule {

    override def checkstyleConfig = Task {
      api.PathRef(T.workspace / "checkstyle-config.xml")
    }
  }
}

Limitations

  • Version 6.3 or above is required for plain and xml formats.

  • Setting checkstyleOptions might cause failures with legacy versions.

CheckstyleXsltModule

This plugin extends the mill.contrib.checkstyle.CheckstyleModule with the ability to generate reports by applying XSL Transformations on a Checkstyle output report.

Auto detect XSL Transformations

XSLT files are detected automatically provided a prescribed directory structure is followed.

/**
 * checkstyle-xslt
 *  ├─ html
 *  │   ├─ xslt0.xml
 *  │   └─ xslt1.xml
 *  └─ pdf
 *      ├─ xslt1.xml
 *      └─ xslt2.xml
 *
 * html/xslt0.xml -> xslt0.html
 * html/xslt1.xml -> xslt1.html
 * pdf/xslt1.xml  -> xslt1.pdf
 * pdf/xslt2.xml  -> xslt2.pdf
 */

Specify XSL Transformations manually

For a custom setup, adapt the following example.

import mill._
import mill.api.PathRef
import mill.contrib.checkstyle.CheckstyleXsltModule
import mill.contrib.checkstyle.CheckstyleXsltReport
import mill.scalalib._

object foo extends JavaModule with CheckstyleXsltModule {

    override def checkstyleXsltReports = Task {
      Set(
        CheckstyleXsltReport(
          PathRef(millSourcePath / "checkstyle-no-frames.xml"),
          PathRef(T.dest / "checkstyle-no-frames.html"),
        )
      )
  }
}

AutoFormatting with Palantir Java Format

Mill supports auto-formatting Java code via the Palantir Java Format project. This can be used on a per-module basis by inheriting from PalantirFormatModule and running the palanatirformat command on that module, or on a global basis by running mill.javalib.palantirformat.PalantirFormatModule/. You can also use --check if you wish to error if the code is not formatted, which is useful in CI validation jobs to ensure code is formatted before merging.

build.mill (download, browse)
package build

import mill._
import mill.javalib.palantirformat._

object `package` extends RootModule with PalantirFormatModule
src/A.java (browse)
public class A {

    public static void main(String[] args) {
    System.out.println("hello"); // indentation should be fixed
    }
}
> ./mill palantirformat --check                                         # check should fail initially
...checking format in java sources ...
...src/A.java
error: ...palantirformat aborted due to format error(s) (or invalid plugin settings/palantirformat options)

> ./mill palantirformat                                                 # format all Java source files
...formatting java sources ...

> ./mill palantirformat --check                                         # check should succeed now
...checking format in java sources ...

> ./mill mill.javalib.palantirformat.PalantirFormatModule/ __.sources   # alternatively, use external module to check/format
...formatting java sources ...

Code Coverage with Jacoco

Mill supports Java code coverage analysis via the mill-jacoco plugin. See the plugin repository documentation for more details: