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
*.propertiesbundle, 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}. Usefilesetsinstead if a subset must be checked or excluded.filesets: explicit file sets to process instead ofsourceDir.locales: a comma/semicolon/whitespace separated list of locales to check the bundles and validation messages against (e.g.en, de). Both theen_USand theen-USnotation 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-DskipTestsswitch 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 theBlahBundleproperties (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.propertiesfor classBlah); - 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.
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(seePdoUtilitiesTest);ValidationBundles, i.e., translations inside validators — those are covered by thevalidationsgoal instead.
Note: due to the fallback nature of resource bundles, a key missing from
bundle_de.propertiesbut provided by the fallbackbundle.propertiesis not reported, because it would still resolve at runtime. The cross-locale diff only reports keys that would actually cause aMissingResourceException.
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.
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¶
- Validation — the annotation-based validation framework whose validators this plugin checks
- Tentackle Maven Plugin — the companion plugin for service- and Java metadata generation, and the source of the common mojo parameters
- Tentackle i18n Maven Plugin — tooling for managing the translation bundles themselves
- Plugin reference (generated)