Google Summer of Code 2026 · Final Report

Java 25 Language Features Support for Checkstyle

Project
Java 25 Language Features Support
Student
Vivek Singh Solanki
Organisation
Checkstyle
Mentors
Roman Ivanov, Mohamed Mahfouz, Stoyan Kostadinov

Project Goals

Java 25 (LTS, September 2025) finalised several language features that Checkstyle could not handle: compact source files with instance main methods (JEP 512) crashed the parser, module import declarations (JEP 511) were parsed but ignored by most checks, and the build itself failed with over a hundred test errors on a JDK 25 toolchain. On top of that, module-info.java files, a Java 9 feature, had never been supported by the grammar at all, which was the single most requested parsing gap in the project.

The project set out to make Checkstyle fully usable on Java 25 code bases. The official deliverables were:

  1. Analysis of the new language features and the parser updates needed to support them. done
  2. Analysis of possible static analysis coverage (new checks) for the new features. done
  3. Updates to existing checks so they produce no false positives, false negatives, or crashes on the new syntax. done
  4. Updates to the build so compilation and all CI jobs run on JDK 25. done
  5. Cleanup of long-standing parsing issues, with module-info.java support as the headline item. done

All work was tracked on the project board and in three umbrella issues, one per language feature, following the structure Checkstyle uses for every new-syntax rollout. Over the program I raised 53 pull requests across checkstyle/checkstyle, checkstyle/contribution and checkstyle/test-configs (48 merged, 2 in final review, 3 regression-config PRs waiting on the new checks to merge) and opened 64 issues (3 feature trackers, 57 child issues, 4 in the tooling repos).

Pull requests

53raised, all repos
48merged, all repos
41merged to checkstyle
7merged to tooling repos
2in final review

Issues

64opened, all repos
3feature audits published
57child issues filed
4tooling repo issues
46already closed

What I Did During GSoC

The work fell into five areas, roughly in the order below, though several ran in parallel.

1. Build and CI on JDK 25

The first thing to fix was the build itself. Running the test suite on JDK 25 produced 106 failures, almost all from Mockito's bundled Byte Buddy not recognising the new class-file version, and a handful from cacio-tta (used for headless GUI tests). I pinned Byte Buddy explicitly and added JDK-conditional Maven profiles for cacio-tta, which brought the suite to green on JDK 25 (#19825). That let us remove the "JDK 25 is not supported" warning that Checkstyle printed at startup (#19884) and document JDK 25 as a supported build JDK (#20994).

While there I added a jdeprscan CI job so we catch usage of deprecated JDK APIs early (#20629, closing a 2019 issue), and requested an ASM 9.8 release from the upstream NonDex project so that CI job could run on JDK 25 as well.

2. Grammar: compact source files and module-info.java

JEP 512, compact source files. Before GSoC started, I had already opened the grammar PR that lets Checkstyle parse a file consisting of top-level methods and fields with no enclosing class (#19343). It introduces a COMPACT_COMPILATION_UNIT root token and a compactMemberDeclaration rule. Getting it merged was the first milestone of the coding period, because every check fix in area 4 depended on it. The design decision that mattered most was to emit a distinct root token rather than a synthetic CLASS_DEF: it keeps the AST honest about what is in the file, and it gives checks a single, cheap way to detect that they are looking at a compact file.

module-info.java. This is the "cleanup parsing issues" deliverable. Checkstyle had rejected modular compilation units since Java 9 (issue #8240, open since 2020). I added grammar support for the full JLS 7.7 directive set (requires, exports, opens, uses, provides, with transitive, static, to, with and open module) plus new tokens MODULE_DEF, DIRECTIVE_BLOCK, REQUIRES, EXPORTS, OPENS, USES, PROVIDES, TO, WITH, LITERAL_OPEN, LITERAL_TRANSITIVE (#20815, merged). A modular compilation unit keeps COMPILATION_UNIT as the root with a MODULE_DEF child, since JLS 7.3 defines it as one alternative of the same production. Following Checkstyle's grammar philosophy, the grammar is a recognizer, not a validator: it accepts a slight superset of legal input and leaves semantic validation to javac.

I also updated the remaining JDK 20 grammar test inputs as part of the parser cleanup (#21209).

3. Analysis: three umbrella issues

For each language feature I wrote a tracker issue that audits every check against the new syntax, following the template Checkstyle uses for language-feature rollouts (JEP review, related tokens, frequently impacted checks, similar checks, review of other static analysis tools, proposed new checks):

  • #19966 Module Import Declarations (JEP 511)
  • #19971 Compact Source Files and Instance Main Methods (JEP 512)
  • #21113 Modular Compilation Units (module-info.java)

The method was the same each time: identify candidate checks by grepping for the affected tokens (OBJBLOCK, CLASS_DEF, COMPILATION_UNIT, IMPORT, …), then run every candidate through the CLI against a minimal file using the new syntax and against an equivalent ordinary file as a control. For module-info.java I went further and ran all 211 checks with haltOnException=false: 134 can structurally never fire on a module file, 15 are file-level and already apply, 62 can fire and most were correct.

Every divergence became a child issue with a reproducer, and each child issue was triaged with the mentors before work started. In total I filed 60 issues in checkstyle/checkstyle between May and August: the 3 trackers plus 57 child issues (11 NPE crashes, 7 false positives, about 20 false negatives, 6 new-check proposals, plus coverage and infrastructure items). Several of the false-negative issues were picked up by other contributors, which was the point of writing them as self-contained, verified issues.

4. Fixing existing checks

Module imports (JEP 511). The grammar already produced MODULE_IMPORT tokens, but three checks silently ignored them. I extended Indentation (new handler in HandlerFactory, #20161), NoLineWrap (#20177) and EmptyLineSeparator (#20178) to treat import module like any other import.

Compact source files (JEP 512). This was the bulk of the coding period. Because top-level members have no enclosing CLASS_DEF or OBJBLOCK, any check that walks getParent() upward or keeps a scope stack keyed on OBJBLOCK was at risk. I fixed:

  • 10 NullPointerException crashes: MethodName, MissingOverrideOnRecordAccessor, MissingOverride, EmptyLineSeparator, UnusedLocalVariable, ModifiedControlVariable, FinalLocalVariable, RequireThis, FinalClass, AbbreviationAsWordInName.
  • 5 false positives: OuterTypeFilename, PackageDeclaration, InvalidJavadocPosition, OuterTypeNumber, UnnecessarySemicolonAfterOuterTypeDeclaration.
  • 4 false negatives: AnnotationLocation, DeclarationOrder, MethodCount, UnnecessarySemicolonAfterTypeMemberDeclaration, plus verified coverage for JavadocStyle.

Each fix followed the same discipline: reproduce with a compact input, add an equivalent ordinary-class input to prove behaviour matches, then fix with the smallest possible change (usually a COMPACT_COMPILATION_UNIT guard at the point where the check assumes an enclosing type).

To make sure this does not regress, I added AllChecksCompactSourceCoverageTest (#20642), which fails CI unless every TreeWalker check has a compact/ input folder whose inline configs exercise every property. Checks that are not yet covered live in a suppression set that acts as a live to-do list (#20590), and I posted three reference PRs showing contributors exactly how to remove one entry (#20672, #20674, #20679).

module-info.java. With the grammar merged, I fixed the two false positives found by the audit, PackageDeclaration (#21126) and InvalidJavadocPosition (#21208), both merged. The second one matters more than it looks: BlockCommentPosition had no MODULE_DEF branch, so a module's Javadoc was not classified as Javadoc at all, which silenced eleven Javadoc checks on module files at once. Of the seven false-negative child issues from the audit, the OpenjdkAnnotationLocation (#21235) and NoLineWrap (#21233) fixes are merged; Indentation, SuppressWarnings, MissingDeprecated and AnnotationLocation are filed and ready to pick up; and the ModifierOrder case was, after discussion with Roman, turned into a proposal for a dedicated RequiresModifierOrder check (#21168).

5. New checks

  • ModuleImportOrder (#21018, in review) enforces where module imports sit relative to regular imports (top or bottom), lexicographic ordering among themselves, and blank-line separation. It was designed as a dedicated check rather than an extension of ImportOrder, which is already at the limit of its configuration surface. During the design we found that Google Java Style now bans module imports outright (section 3.3.1.1), so the proposed CustomImportOrder MODULE group (#18419) remains under discussion; I proposed closing it as unnecessary.
  • ModuleDirectiveOrder (#21167, in review) enforces the directive ordering in module-info.java (requires, exports, opens, uses, provides by default, configurable), grouping, and separation, matching what IntelliJ and google-java-format do.
  • Proposed and approved for follow-up: AvoidModuleImport (#19968), RequiresModifierOrder (#21168), MissingJavadocModule (#21165). A fourth proposal, CompactSourceFile (#20229), was closed after review because IllegalToken configured with COMPACT_COMPILATION_UNIT already does the job.

Supporting tooling

New checks and grammar changes have to be validated against real-world code before merge. I fixed the ANTLR regression report so it scans the PR branch instead of the fork's master and can target a specific project from a PR comment (#21065), fixed an OutOfMemoryError in the diff report post-processor on large projects and made diff mode install the base-branch artifact (test-configs #251, #253), and added the apereo-cas project to the regression lists because it is one of the few large open-source code bases that already uses import module (test-configs #259, contribution #1118).

Current Status and Future Work

All five deliverables are complete:

  1. Analysis of new language features and parser updates. Three feature audits published (#19966, #19971, #21113); grammar for compact source files (#19343) and module-info.java (#20815) merged.
  2. Analysis of static analysis coverage (new checks). Six new checks proposed; ModuleImportOrder and ModuleDirectiveOrder implemented and in final review, three more filed as approved issues, and one (CompactSourceFile) closed because an existing check already covers it.
  3. Updates to existing checks. All 11 crashes and all 7 false positives found by the audits are fixed and merged (10 crashes and all 7 false positives by me, one crash by another contributor), plus the three JEP 511 gaps and four JEP 512 false negatives. A CI test now enforces compact-source coverage for every check so nothing regresses.
  4. Build and CI on JDK 25. Test suite green on JDK 25, startup warning removed, jdeprscan job added, JDK 25 documented as a supported build JDK.
  5. Cleanup of parsing issues. module-info.java, unsupported since Java 9, now parses; the two false positives it exposed are fixed and merged; JDK 20 grammar inputs updated.

How the scope shifted, and why

The original proposal budgeted the second half of the summer for the long tail of JEP 512 false negatives. Once the grammar landed and every crash and false positive was fixed, the mentors and I made a deliberate decision: the remaining false negatives are on the easier side, already documented in detail, and open for any contributor to pick up, while module-info.java support and the module checks were high-impact work that nobody else was going to do. So I moved onto the harder, higher-value items and offloaded the false-negative tail to the community, as the proposal had planned for.

That handoff was engineered, not just announced. Every false negative has a verified child issue with a reproducer under #19971, the coverage tracker #20590 gives contributors a step-by-step recipe, three reference PRs show exactly what a finished contribution looks like, and AllChecksCompactSourceCoverageTest turns the remaining list into a failing CI test rather than a wiki page. Several of those issues have already been picked up and closed by other contributors during the program.

Forty-one pull requests were merged to checkstyle/checkstyle during the program, plus seven to checkstyle/contribution and checkstyle/test-configs. Two are open in final review: the two new checks.

Next steps

  • Land the two new checks in review, ModuleImportOrder and ModuleDirectiveOrder.

I intend to keep working on all of these as a Checkstyle maintainer.

Code Contributions

Everything is on the GSoC 2026 project board. Full list of my PRs: checkstyle/checkstyle.

Build and CI on JDK 25

  • #19825Pin byte-buddy and add JDK-conditional cacio-tta profiles (fixes 106 test failures on JDK 25)merged
  • #19884Remove obsolete "JDK 25 not supported" warningmerged
  • #20994Document JDK 25 as supported for building Checkstylemerged
  • #21263Document 14.x in the JRE matrix and Java 25 language support on the site indexmerged
  • #20629Add jdeprscan CI job to detect deprecated JDK API usagemerged
  • NonDex #213Upstream request: release with ASM 9.8 for JDK 25upstream

Grammar

  • #19343Add grammar support for JDK 25 compact source files (JEP 512)merged
  • #20815Add grammar support for module-info.javamerged
  • #21209Update grammar inputs for JDK 20merged

Analysis (umbrella issues)

  • #19966Module Import Declarations (JEP 511)tracker
  • #19971Compact Source Files and Instance Main Methods (JEP 512)tracker
  • #21113Modular Compilation Units (module-info.java)tracker
  • #20590Compact source input coverage tracker for all checkstracker

JEP 511 module import fixes

  • #20161Indentation: support module import declarationsmerged
  • #20177NoLineWrap: add MODULE_IMPORT tokenmerged
  • #20178EmptyLineSeparator: add MODULE_IMPORT supportmerged

JEP 512 compact source files: crashes

  • #20251MethodNamemerged
  • #20253MissingOverrideOnRecordAccessormerged
  • #20256MissingOverridemerged
  • #20260EmptyLineSeparatormerged
  • #20261UnusedLocalVariablemerged
  • #20305ModifiedControlVariablemerged
  • #20306FinalLocalVariablemerged
  • #20326RequireThismerged
  • #20543FinalClassmerged
  • #20546AbbreviationAsWordInNamemerged

JEP 512 compact source files: false positives

  • #20240OuterTypeFilenamemerged
  • #20247PackageDeclarationmerged
  • #20354InvalidJavadocPositionmerged
  • #20370OuterTypeNumbermerged
  • #20404UnnecessarySemicolonAfterOuterTypeDeclarationmerged

JEP 512 compact source files: false negatives

  • #20407AnnotationLocationmerged
  • #20408DeclarationOrdermerged
  • #20422MethodCountmerged
  • #20427UnnecessarySemicolonAfterTypeMemberDeclarationmerged
  • #20548JavadocStyle (verified, tests added)merged

JEP 512 test coverage infrastructure

  • #20642Add test enforcing compact source input coverage for all checksmerged
  • #20672Reference coverage PR: OuterTypeFilenamemerged
  • #20674Reference coverage PR: StringLiteralEqualitymerged
  • #20679Reference coverage PR: NeedBracesmerged
  • contrib #1069Exclude compact source inputs from local-checkstyle regressionmerged
  • contrib #1072Exclude compact source inputs from local-checkstyle regressionmerged
  • contrib #1076Exclude compact source inputs from local-checkstyle regressionmerged

module-info.java check fixes

  • #21126PackageDeclaration false positive on module-info.javamerged
  • #21208InvalidJavadocPosition false positive on module Javadocmerged
  • #21233NoLineWrap false negative on module declarationsmerged
  • #21235OpenjdkAnnotationLocation: add module declaration supportmerged
  • #21272Follow-up to #21208: regroup module-info test inputs and address deferred review itemsmerged
  • #21160Indentation: no support for module declarationsopen issue
  • #21163SuppressWarnings: no support for module declarationsopen issue
  • #21164MissingDeprecated: no support for module declarationsopen issue
  • #17172AnnotationLocation: no support for module declarationsopen issue
  • #21166ModifierOrder false negative on requires modifiers, closed in favour of new check RequiresModifierOrdersuperseded

New checks

Regression tooling

  • #21065Fix ANTLR report PR sources and add project argumentmerged
  • test-configs #251Fix OutOfMemoryError in postProcessCheckstyleReport for large projectsmerged
  • test-configs #253Install base branch artifact in diff modemerged
  • test-configs #259Add apereo-cas project to regression testing listsmerged
  • contrib #1118Add apereo-cas project for module import diff reportsmerged

What I Learned During GSoC

  • Grammar design under constraints. Adding a new compilation-unit shape to a mature ANTLR grammar taught me to think about the AST as an API: the choice of root token for compact files, and of keeping COMPILATION_UNIT as root for module files, shaped every downstream fix. I also internalised the "recognizer, not validator" rule.
  • Auditing at scale. Running every check against a new syntax and turning each divergence into a verified, reproducible child issue is slow up front but pays back many times over. Well-specified issues let other contributors take work in parallel, and the audits themselves became the reference for the feature.
  • Making correctness enforceable. A test that fails CI when a check lacks compact-source coverage did more for long-term quality than any individual fix; converting a to-do list into a failing test is a pattern I will reuse.
  • Working with a large review process. Every PR went through strict CI (pitest, regression diff reports, xdoc generation) and multiple maintainer reviews. I learned to keep changes surgical, to generate the regression report before being asked, and to argue design points with proper reasoning rather than opinion.
  • Scoping. Handing the false-negative tail to the community and spending that time on module-info.java, and proposing to close #18419 once the Google style guide made it moot, were both better outcomes than following the original plan literally.

Acknowledgements

I want to thank my mentors, Roman Ivanov, Mohamed Mahfouz and Stoyan Kostadinov. Roman, as org admin and mentor, trusted me with a large project, kept the scope honest, and taught me how Checkstyle thinks about compatibility and long-term maintenance. Mohamed reviewed nearly every one of these PRs, pushed back on grammar and AST design until it was right, and was always available on Discord when I was stuck. Stoyan reviewed all of my PRs alongside Mohamed, with a sharp eye for edge cases and test coverage, and kept the review queue moving throughout the summer. Thanks as well to the contributors who picked up child issues from the audits.

Google Summer of Code was a great experience, and I am glad to keep working on Checkstyle after it.