Skip to content

Check Maven Plugin

The Tentackle Checker Maven Plugin

The tentackle-check-maven-plugin performs build-time consistency checks that a normal compiler cannot catch. It verifies two kinds of string references that are only resolved at runtime and would otherwise fail silently — or worse, in front of the user — when a translation or a script is missing:

  • that every resource-bundle key referenced from the Java sources actually exists in the corresponding *.properties bundle, for all checked locales, and
  • that every annotation-based validation is well-formed: its validator can be instantiated, its scripts compile, and its (possibly i18n-scripted) message resolves for every checked locale.

Both checks are fail-fast: as soon as a key or a validator cannot be resolved, the goal logs the offending location and fails the build. This turns a class of runtime MissingResourceExceptions into compile-time errors.

The goals share the prefix tentackle-check, so they can be invoked directly from the command line, e.g., mvn tentackle-check:bundles.

How It Works

Both goals are annotation-processing mojos: they run an annotation processor over the project's sources (not the byte code) and inspect the parsed syntax trees. They are therefore bound to the process-classes phase and require the project's compile dependencies to be resolved, because they load the referenced bundles, validator classes, and scripting engines from the project classpath.

Because the checks operate on the source tree, classes that must not be checked have to be excluded from the file set rather than annotated.

Prerequisites

The plugin requires the same JDK and Maven versions as the rest of the framework (JDK >= 25, Maven >= 3.9.0). It is platform independent. As the checks load classes and resources from the project, they need the compile classpath resolved, which Maven does automatically for the process-classes binding.

Overview of Goals

Goal Default phase Purpose
bundles process-classes Verify that all getString(...) bundle keys found in the sources exist in the bundles, for all checked locales.
validations process-classes Verify that all @Validation-based validators instantiate and that their value/condition/message scripts compile and resolve.
help Print plugin usage information (generated by the maven-plugin-plugin).

Neither goal is bound to a Tentackle lifecycle by default — you add the executions you need to the modules that contain bundles resp. validations.

Common Configuration

Like all Tentackle mojos, the goals inherit the common parameters verbosity, skip and charset (see the tentackle-maven-plugin docs). On top of that, AbstractCheckMojo adds the parameters shared by both check goals:

  • sourceDir (tentackle.sourceDir): directory holding the sources to process. Defaults to ${project.build.sourceDirectory}. Use filesets instead if a subset must be checked or excluded.
  • filesets: explicit file sets to process instead of sourceDir.
  • locales: a comma/semicolon/whitespace separated list of locales to check the bundles and validation messages against (e.g. en, de). Both the en_US and the en-US notation are accepted. The fallback locale should come first. If omitted, the build's default locale is used (and a warning is logged).
  • skipTests (${skipTests}): skips the check, so the standard -DskipTests switch disables it together with the unit tests.
  • scriptingLanguage: sets the default scripting language used to evaluate validator scripts, for the case that a validator's value, condition, or message is expressed as a script without an explicit language prefix.

A typical configuration registers both checks on a module that ships bundles and validations:

<plugin>
  <groupId>org.tentackle</groupId>
  <artifactId>tentackle-check-maven-plugin</artifactId>
  <version>${project.version}</version>
  <executions>
    <execution>
      <id>check-bundles</id>
      <goals>
        <goal>bundles</goal>
      </goals>
    </execution>
    <execution>
      <id>check-validations</id>
      <goals>
        <goal>validations</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <locales>en, de</locales>
  </configuration>
</plugin>

The bundles Goal

Scans the sources for every invocation of <reference>.getString("<string-literal>") and checks that the referenced key exists in the matching bundle, loaded for each configured locale. The bundle a reference belongs to is derived from the <reference>:

  • a bundle class name, e.g. BlahBundle.getString("foo"), is checked against the BlahBundle properties (resolving imports to find the fully-qualified class);
  • a method call such as getBundle().getString("foo") is checked against the bundle named after the enclosing class (Blah.properties for class Blah);
  • a variable or constant reference such as resources.getString("foo") is treated the same way — the bundle name is that of the enclosing class.

Only referenced bundles are loaded (the goal first collects the used bundle names, then loads the bundles registered via @Bundle/@FxControllerService annotations through a META-INF/bundles file), so the check stays fast even with many bundles on the classpath.

Beyond checking individual keys, the goal also diffs the key sets across the configured locales and reports keys that are present in one locale but missing in another — catching translations that would throw a MissingResourceException at runtime.

mvn tentackle-check:bundles -Dlocales=en,de

What is not covered

The check is deliberately limited to string literals, because only those can be resolved statically. The following must be covered by ordinary unit tests instead:

  • non-literal keys, e.g. getBundle().getString(text), as frequently found in enums;
  • PdoBundles, i.e., translations for @Singular/@Plural (see PdoUtilitiesTest);
  • ValidationBundles, i.e., translations inside validators — those are covered by the validations goal instead.

Note: due to the fallback nature of resource bundles, a key missing from bundle_de.properties but provided by the fallback bundle.properties is not reported, because it would still resolve at runtime. The cross-locale diff only reports keys that would actually cause a MissingResourceException.

The validations Goal

Scans for validator annotations (those meta-annotated with @Validation), instantiates the validator for each occurrence, and verifies its three compound-value parameters — value, condition and message:

  • if any of them is a script, the script is compiled (and validated), catching syntax errors at build time;
  • if the message is an i18n-script (see @MessageScriptConverter), it is additionally evaluated for every configured locale, so a missing translation is reported as an error rather than surfacing later as a runtime exception.

The goal reports the number of annotations and scripts checked per locale and fails the build if any validator fails to instantiate or any script fails to compile or resolve.

mvn tentackle-check:validations -Dlocales=en,de

Use scriptingLanguage if your validators use scripts without an explicit language prefix, and you want to pin the default engine for the check.

How It Fits Into the Build

Both goals run in process-classes, i.e., after compilation but before tests and packaging, so a broken translation or validation script breaks the build early — locally and in CI alike. Because they share the skipTests switch with the unit tests, a mvn install -DskipTests skips them too, which keeps fast inner-loop builds fast while still guarding the full build and the CI pipeline.

A common arrangement is to enable the bundles goal in every module that carries UI- or message bundles, and the validations goal in the domain modules that define validators, listing the production locales (fallback first) in the shared plugin configuration.

Further Reading