Using Eclipse Java Compiler (Ecj) in Maven Builds

Using Eclipse Java Compiler (ecj) in maven builds

The Eclipse Java Compiler (ecj) has a lot of advantages over the standard javac compiler. It is fast, and it has way more warnings and errors that can be configured, improving code quality. One of the most interesting things in the compiler is the addition of null types inside the compiler: by annotating your code with @Nullable and @NotNull annotations you can force the Eclipse compiler to check null accesses at compile time instead of runtime. When applied rigorously this teaches you to code way more safe (by preventing null values) and it prevents NPE exceptions during testing or production.

To use the Eclipse Compiler inside Maven is not too hard, but there is a lot of misinformation and old information on the Internets which causes a lot of confusion. I hope this helps to set things straight.

To make Maven use the ecj compiler you need to use the plexus-compiler-eclipse plugin and nothing else. A typical configuration would be the following:

<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<compilerId>eclipse</compilerId>
<source>${source.jdk.version}</source>
<target>${target.jdk.version}</target>
<!-- Passing arguments is a trainwreck, see https://issues.apache.org/jira/browse/MCOMPILER-123 -->
<compilerArguments>
<properties>${project.basedir}/.settings/org.eclipse.jdt.core.prefs</properties>
</compilerArguments>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>

<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.8.3</version>
</dependency>

<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
<version>3.13.101</version>
</dependency>
</dependencies>
</plugin>
</pluginManagement>

Put this part in either the pluginManagement or the build section of your parent/root pom.

Now let's explain the different parts ;)

The maven-compiler-plugin needs to be of a recent version. The source and target parameters define the versions of java to use for source code and bytecode, and are usually the same.

Passing arguments to the compiler is an utter trainwreck. See the separate section on that below here. In this example I use the properties setting which allows me to provide detailed settings on which errors and warnings I want to have when compiling things. By using the ${project.basedir} variable inside the parameter I have these settings per project: every project is required to have a .settings/org.eclipse.jdt.core.prefs file present (which is by happy chance the location where the Eclipse IDE leaves its compiler settings).

The dependency on plexus-codehaus-eclipse defines the plugin that knows how to run the Eclipse compiler. The 2.8.3 version was the latest at the time of writing but this version has a few issues. Version 2.8.4 should come with a rewritten interface to the compiler which fixes a lot of issues, but this version is still in the works at the time of writing. You can find details on the plugin here, so progress can be followed on new releases/code changes.

The other important dependency is the org.eclipse.jdt:ecj dependency: this one specifies the exact version of the ecj compiler to use. You should always specify it because otherwise build stability will suffer when the plugin decides to use another version of the compiler one day before you have a big release ;) The version number to use for the ecj compiler is a bit of a problem. You might be able to find the version number from the list of releases and then check this maven repository for something that looks like it. But this repository only contains the older versions. When you need a more recent release you should apparently look here at this one - this is where Eclipse currently pushes its versions. This newer repository does away with the easily recognizable version numbers of the earlier one; it uses version numbers like 3.1x.x as seen above. Eclipse usually has a major release once every year plus one or two fix releases in between. The second part in the 3.13.x number corresponds to the internal versioning used inside the Eclipse Platform project for releases. It is hard to get by a list but at least these are known:

Version    Eclipse Release      Compiler Version
3.13.0 Oxygen Release 4.7
3.13.50 Oxygen 1a 4.7.1a
3.13.100 Oxygen R2 4.7.2

The version always starts with 3, the 13 is more or less the "year" of the release. So when 13 is Oxygen (2017, 4.7) 14 will probably be Photon (2018, 4.8).

Versions of the plexus-compiler-eclipse plugin: before 2.8.4

Versions before 2.8.4 of the plexus-compiler-plugin used an internal API to start the Eclipse compiler. This causes a lot of things not to work that well, as this internal API, for instance, does not interpret the usual command line parameters of the ecj compiler. This makes it quite hard to use, and some things are not supported. The following is a list of restrictions:

  • Annotations processing is not implemented. Any configuration is silently ignored.

  • Adding specific parameters by using the <compilerArguments> tag is hard as there are multiple problems with the implementation:

  • The compiler mojo seems to add dashes to all parameters entered here. The internal API used by this version of the plugin, however, needs parameters without dashes. So the plugin removes them again.
    As the parameters here are not really command line ecj parameters it is hard to know which ones to use: look at the Compiler.java class and CompilerOptions.java classes inside Eclipse's source code for details.

  • The plugin DOES accept some parameters there, but these are interpreted by the plugin itself and then "translated" to the internal api.

This plugin accepts the following parameters in the <compilerArguments>> tag:

  • <properties>filename</properties>: defines a properties file that will be passed to the -properties parameter of the compiler. Examples of this file's format can be found by looking at the file .settings/org.eclipse.jdt.core.prefs in an Eclipse project: this file stores the compiler's configuration. It contains settings for warnings, errors and informational messages plus compiler compliance settings.

  • <errorsAsWarnings>whatever</errorsAsWarnings>. When this is valid the plugin will ignore any error that is generated by the compiler and report them as warnings. Of course compilation still failed so depending on the error a .class file might have been written/updated or not. This gets handled by the plugin itself: it just changes all errors to warnings and tells the world that compilation worked.

From 2.8.4

Version 2.8.4 of the plexus-compiler-eclipse plugin has been mostly rewritten. It now uses the public API of the ECJ compiler which more or less is the ECJ compiler itself. This for instance means that everything that ECJ can do (like annotations processing) the plugin can now do too, and parameters entered in the tag are now passed to the compiler, which means you should be able to use ecj's help page to find out interesting parameters to add.

Like the previous version this version also requires you to remove the '-' from all parameter names; the dash is automagically added again before the parameter name is added to the ecj command line.

This version supports annotation processing as defined by Maven; by adding the required parts to the compilation blob you can have your annotation processors run. For example:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<annotationProcessors>
<annotationProcessor>db.annotationprocessing.EntityAnnotationProcessor</annotationProcessor>
</annotationProcessors>
<annotationProcessorPaths>
<dependency>
<groupId>to.etc.domui</groupId>
<artifactId>property-annotations-processor</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
</annotationProcessorPaths>
</configuration>

<dependencies>
<dependency>
<groupId>to.etc.domui</groupId>
<artifactId>property-annotations-processor</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>

This part may seem incomplete because there is no reference to the plexus-compiler-eclipse plugin at all, but remember that in Maven that configuration inherits: the parent POM in this case contained the part above, and this just adds a bit of configuration for this POM's project only.

Building maven projects with JDT Core compiler

I got it working:

  • maven-compiler-plugin#non-javac-compilers

    • -> Plexus-Compiler

      • -> Plexus-Eclipse-Compiler
  1. Use the documented plugin/configuration style.
  2. Adjust the version, that suits you best (I had "issues" with lower (than 2.8.8) and 2.9.0 versions).

like so:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerId>eclipse</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.8.8</version> <!-- => ecj.3.2.2 -->
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

with:

mvn clean compile -X

we get:

Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: C:\Program Files\NetBeans-12.5\netbeans\java\maven\bin\..
Java version: 16.0.2, vendor: Oracle Corporation, runtime: C:\Program
Files\Java\jdk-16.0.2
Default locale: de_DE, platform encoding: UTF-8
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows
...
...
-- end configuration --
Using compiler 'eclipse'.
Adding ...
CompilerReuseStrategy: reuseCreated
useIncrementalCompilation enabled
Stale source detected: ...
Changes detected - recompiling the module!
Classpath:
...
Source roots:
...\src\main\java
...\target\generated-sources\annotations
incrementalBuildHelper#beforeRebuildExecution
Using JSR-199 EclipseCompiler
ecj: using character set UTF-8
ecj command line: [-noExit, -preserveAllLocals, -g:lines,vars,source, -source, 1.6, -target, 1.6, -encoding, UTF-8, -warn:none, -d, ...\target\classes, -classpath, ...\target\classes;...\target\classes;]
ecj input source files: [C:\DEV\projects\...\Application.java]

incrementalBuildHelper#afterRebuildExecution
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.254 s
Finished at: 2021-11-18T18:03:02+01:00
------------------------------------------------------------------------

To use(try!;) an alternative ecj version, we can:

<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerId>eclipse</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.8.8</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
<version>3.27.0</version> <!-- 09/15/2021 -->
</dependency>
</dependencies>
</plugin>
  • ECJ Maven artifact
  • Plexus-Eclipse-Compiler@maven central

Is the standalone ecj (Eclipse Java Compiler) package no longer maintained?

The official coordinates of ecj in Maven are indeed: org.eclipse.jdt:ecj.

As you can see, this artifact exists in all release versions since 3.12.3 (corresponding to Neon.3).

Artifacts in other groups may have mistakenly used the version number of the Eclipse release, not that of ecj itself, thus appearing to be newer even if they are (a lot) older.

(When Eclipse SDK was bumped up from 3.x to 4.x this was due to breaking changes in the UI part of the code. ecj, however, remained compatible and thus stayed at 3.x)

In case of doubt, run the following to see the real version of ecj:

$ java -jar ecj.jar -version

The latest release version will answer

Eclipse Compiler for Java(TM) v20191203-2131, 3.20.0, Copyright IBM Corp 2000, 2015. All rights reserved.

Is Eclipse's ecj compiler extensible?

As Object Teams has been mentioned in comments:

(1) Object Teams itself extends JDT for its own language OT/J which is an extension of Java. This is done in a dual strategy:

  • We maintain a fork of org.eclipse.jdt.core. While this is quite heavy lifting it successfully demonstrates that the JDT architecture is suitable for modification.
  • We use our own concepts of role objects to non-invasively adapt the behavior of other parts of the IDE (notably org.eclipse.jdt.ui) to reflect the semantics of OT/J

(2) I have a few (oldish) blog posts that demonstrate how OT/J can be used for creating non-invasive variants of JDT including support for extended syntax:

  • IDE for your own language embedded in Java? (part 1)
  • IDE for your own language embedded in Java? (part 2)
  • Get for free what Coin doesn’t buy you

Disclaimer: I am author of OT/J and lead of its implementation, and later became a committer on Eclipse JDT.

For further questions, there's a forum.

Fatal error compiling: Failed to run the ecj compiler: Unrecognized option : --module-version

Until now ecj does not support the option --module-version.

It may have fallen through the cracks for several possible reasons:

  • nobody cared, because the module version is not evaluated by JVM nor other tools
  • perhaps it wasn't specified as a new compiler option before Java 9 GA (http://openjdk.java.net/jeps/261 does not have a version history, but I know that the text was changed significantly right on the release day).

Please file a feature request at https://bugs.eclipse.org/bugs/enter_bug.cgi?product=JDT

Edit: Your feature request has been implemented. While we missed today's release of Eclipse 2019-06, you may fetch ecj.jar from the next integration build below https://download.eclipse.org/eclipse/downloads/index.html - next full release is scheduled for September.

Edit2: After more research the simplest workaround might be to go back to version 3.8.0 of maven-compiler-plugin which does not try to pass --module-version to the compiler. This happens only since 3.8.1 released this May.

Create ecj.jar file in Eclipse

How to build ecj.jar depends on what build technology you use.

If you have the context configured so that you can build org.eclipse.jdt.core with Maven/Tycho, then you should adjust the batch-compiler execution within the project's pom.xml.

Otherwise, a more light-weight approach should be to invoke Ant with scripts/export-ecj.xml. That file may be a bit out-of-date, but you could take a look at how this is adapted for creating another variant of ecj (for OT/J), see: http://git.eclipse.org/c/objectteams/org.eclipse.objectteams.git/tree/org.eclipse.jdt.core/scripts/export-ecj.xml.

For OT/J this script is invoked from the Ant-based PDE-build process, so for standalone invocation a bunch of properites needs to be configured first.

  • integrating apt and tool requires those sibling projects to sit in expected locations, and properties like build.root.dir set. Otherwise, commenting the section for apt and tool should get you going.
  • set buildLabel to something useful

With these preparations I can successfully run Ant from within the IDE.



Related Topics



Leave a reply



Submit