checkstyle / checkstyle/checkstyle
Add Check Support for Java 25 Module Import Declarations Syntax
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 4.2k
- Avg merge
- 22h 23m
- Merged PRs (30d)
- 232
Description
This issue is the main tracker for integrating support for **Module Import Declarations** (Java 25, JEP 511) into Checkstyle.
**Status of grammar support:** lexer and parser already implemented. `LITERAL_MODULE` and `MODULE_IMPORT` tokens are defined in `JavaLanguageLexer.g4` and produced by `JavaLanguageParser.g4`.
**Syntax under analysis:**
```java
import module java.base;
import module java.sql;
void main() {
var names = List.of("Alice", "Bob");
var conn = DriverManager.getConnection(...);
}
```
---
## 1.1 Review of Java Enhancement Proposals (JEPs)
**JEP timeline:** [JEP 476](https://openjdk.org/jeps/476) first preview (Java 23), [JEP 494](https://openjdk.org/jeps/494) second preview (Java 24), [JEP 511](https://openjdk.org/jeps/511) final (Java 25).
**What the JEP introduces:** A module import declaration has the form `import module M;`. It imports, on demand, all of the public top-level classes and interfaces in:
- the packages exported by the module M to the current module, and
- the packages exported by the modules that are read by the current module due to reading the module M.
Per the JLS delta accompanying JEP 511, single-module-import declarations have the weakest shadowing precedence: they are shadowed by type-import-on-demand declarations, which are shadowed by single-type-import declarations.
---
## 1.2 Similar and Related Tokens
### Method
Candidate impacted checks were identified by:
```bash
checks=src/main/java/com/puppycrawl/tools/checkstyle/checks
{
grep -rl "IMPORT" $checks
grep -rl "STATIC_IMPORT" $checks
} | sed "s|^$checks/||" | sort -u
```
Each candidate was then run via the Checkstyle CLI against a minimal file containing `import module java.base;` and against a sanity-check file with the equivalent regular import (`import java.util.List;`).
### Import-family checks (sub-tracked under #18127)
| Check | Verified status | Reference |
|---|---|---|
| `IllegalImportCheck` | **Done.** New `illegalModules` property added; references `TokenTypes.MODULE_IMPORT` | #18207 / PR #18220 (merged) |
| `RedundantImportCheck` | **Done.** References `TokenTypes.MODULE_IMPORT` | #18171 / PR #18195 (merged) |
| `ImportControlCheck` | **Done.** References `TokenTypes.MODULE_IMPORT` ; added `ModuleImportRule` helper class. | #18329 (merged) |
| `CustomImportOrderCheck` | **Pending child issue.** | #18419 (open) |
| `ImportOrderCheck` | **Covered by new dedicated check** (see §1.4, `ModuleImportOrder`) rather than extending this already-complex check. | #18423 (open) |
| `UnusedImportsCheck` | **No change needed.** Documentation-only : "Checkstyle does not resolve modules and therefore cannot determine which packages or types are brought into scope by an import module declaration." | PR #18216 (merged) |
| `AvoidStarImportCheck` | **No change needed.** `import module` syntax has no `.*` form, so the check is structurally inapplicable. A separate new check (`AvoidModuleImport`, see §1.4) covers the analogous prohibition. | rationale in #18127 |
| `AvoidStaticImportCheck` | **No change needed.** Module imports are not static imports. | rationale in #18127 |
---
## 1.3 Frequently Impacted Checks
| Check | Verified behaviour on `import module …;` | Status |
|---|---|---|
| **IllegalToken** | Configured with `tokens="LITERAL_MODULE,MODULE_IMPORT"`. Correctly logs two violations on `import module java.base;`. `getAcceptableTokens()` returns `TokenTypes.values()`, so the new tokens are automatically configurable. | No change required |
| **IllegalTokenText** | `module` is parsed as the keyword `LITERAL_MODULE`, not as `IDENT`. The check's acceptable tokens are a fixed literal/identifier/comment set, by design; keywords are out of scope. Users wanting to ban the `module` keyword should use `IllegalToken` instead. | No change required |
| **Indentation** | A file with ` import module java.base;` (6-space indent) produces **no violation**. The same wrong indent on a regular `import java.util.List;` correctly reports `'import' has incorrect indentation level 6, expected level should be 0`. Root cause: `HandlerFactory` registers `ImportHandler` only for `TokenTypes.IMPORT`; there is no handler for `MODULE_IMPORT`. **False negative.** | Child issue: https://github.com/checkstyle/checkstyle/issues/20108 |
| **Whitespace family, `NoLineWrapCheck`** | A wrapped `import\n module\n java.base;` produces **no violation**. Sanity check with a wrapped regular import correctly reports the wrap. Root cause: `getAcceptableTokens()` lists `IMPORT`, `STATIC_IMPORT`, `PACKAGE_DEF`, etc., but **not** `MODULE_IMPORT`. **False negative.** | Child issue: https://github.com/checkstyle/checkstyle/issues/20109 |
| **Whitespace family, `EmptyLineSeparatorCheck`** | Missing blank line between an `import module` and a following class declaration is **not** flagged. The same layout with a regular import correctly reports `'CLASS_DEF' should be separated from previous line.` Root cause: `getAcceptableTokens()` includes `IMPORT` / `STATIC_IMPORT` but not `MODULE_IMPORT`, so `processImport` never runs on a module-import node. **False negative.** | Child issue: https://github.com/checkstyle/checkstyle/issues/20111 |
---
## 1.4 Discover Similar Checks
- `AvoidStarImportCheck` is the existing analog that prohibits one whole *style* of import. A module import is a higher-fan-out on-demand form (one declaration imports every package the module exports), so a parallel check is warranted rather than extending `AvoidStarImport` (different token, different configuration semantics).
- `ImportOrderCheck` is already at the upper limit of configuration complexity. The cleaner path is a separate dedicated check that handles module imports in isolation.
These two analogs drive the new-check proposals in §3.
---
## 1.5 Review of Other Static Analysis Tools
| Tool | What it ships for JEP 511 |
|---|---|
| **IntelliJ IDEA** | No named inspection. Support is delivered as IDE actions documented in the [JetBrains Guide page on module imports](https://www.jetbrains.com/guide/java/tips/module-import/): when *Optimize Imports* runs, IntelliJ automatically removes any individual imports that are already covered by an `import module` declaration in the file; the Context Action *Replace with single class imports* (Alt+Enter) converts a module import back into the equivalent single-class imports; and an opt-in *Delete unused module imports* setting lets Optimize Imports remove unused module imports as well. The separate [JetBrains blog post (July 2025)](https://blog.jetbrains.com/idea/2025/07/module-import-declarations-no-more-import-hell/) states the stylistic position explicitly: *"I'd recommend not replacing all the individual import statements or package statements with import module statements in your codebase."*
| **PMD** | No lint rule. PMD 7.16.0 (2025-07-25) added Java 25 language support and promoted `ASTImportDeclaration#isModuleImport()` to stable API so users can author custom rules, but ships no built-in JEP 511 rule. |
---
## 1.7 Good Source of Best Practices
- [JEP 511](https://openjdk.org/jeps/511): normative spec; defines the syntax and semantics.
- [JLS delta for JEP 511](https://cr.openjdk.org/~gbierman/jep511/jep511-20250422/specs/module-import-declarations-jls.html): gives the two rules a non-type-aware checker can rely on:
- (1) **Redundancy**: multiple single-module-import declarations naming the same module are redundant, "the effect is as if that module was imported only once." Directly enforceable by `RedundantImportCheck` (already done, #18171).
- (2) **Shadowing precedence**: single-module-import is the weakest form (never shadows other declarations, shadowed by every other import kind). *Directly* enforceable consequences are limited to placement/ordering rules (motivates `ModuleImportOrder`) and ban-the-form rules (motivates `AvoidModuleImport`). Detecting actual shadowing requires module-graph + type resolution and is out of scope (see §1.7's not-enforceable list).
- [JetBrains blog: "Module Import Declarations: No More Import Hell"](https://blog.jetbrains.com/idea/2025/07/module-import-declarations-no-more-import-hell/): explicit style recommendation: *"I'd recommend not replacing all the individual import statements or package statements with import module statements in your codebase."* This is the closest thing to an industry style-guide statement and directly motivates the proposed `AvoidModuleImport` check.
**Enforceable by Checkstyle** (syntactic, token-level):
1. Duplicate `import module M;` declarations (already covered by `RedundantImportCheck`, #18171).
2. Placement and ordering of module imports (drives `ModuleImportOrder`, §3).
3. Banning or restricting the feature, with optional module allow/deny lists (drives `AvoidModuleImport`, §3).
**Not enforceable** (requires type-awareness / module-graph resolution):
- Detecting shadowed or unused module imports.
- Suggesting `import module M` to replace many on-demand imports.
---
## Identified Work Items
### Import-family child issues (sub-tracked under #18127)
- `CustomImportOrderCheck`, #18419 (pending)
- `ImportOrderCheck`, #18423 (superseded by `ModuleImportOrder` below)
### Frequently-impacted check fixes (new child issues to open)
- `IndentationCheck`: register an `ImportHandler` for `MODULE_IMPORT` in `HandlerFactory`. #TBD
- `NoLineWrapCheck`: add `TokenTypes.MODULE_IMPORT` to default + acceptable tokens. #TBD
- `EmptyLineSeparatorCheck`: add `TokenTypes.MODULE_IMPORT` to acceptable tokens and handle it analogously to `IMPORT`/`STATIC_IMPORT` in `checkToken()`. #TBD
### Proposed new checks
- **AvoidModuleImport**, [#19968](https://github.com/checkstyle/checkstyle/issues/19968). Analogous to `AvoidStarImport`. A module import is a higher-fan-out on-demand import; teams that ban `*` imports may want to ban `import module` for the same reasons (readability).
- **ModuleImportOrder**, [#19967](https://github.com/checkstyle/checkstyle/issues/19967). Enforce ordering, grouping, and placement of `import module` declarations (e.g. module imports come before regular and static imports, are grouped together, sorted alphabetically by module name). Preferred over extending `ImportOrderCheck`, which has already grown very complex.
Contributor guide
Assessment
This issue has not been assessed yet.