checkstyle / checkstyle/checkstyle

Add Check Support for Java 25 Module Import Declarations Syntax

Open
#19,966 6 comments 0 reactions 1 assignee Claimed by @vivek-0509 View on GitHub
approved
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.