Skip to content

[Java] Remove unused and duplicate imports from generated code - #25157

Draft
ondrej-simon wants to merge 2 commits into
OpenAPITools:masterfrom
ondrej-simon:b/remove-unused-imports-poc
Draft

ondrej-simon wants to merge 2 commits into
OpenAPITools:masterfrom
ondrej-simon:b/remove-unused-imports-poc

Conversation

@ondrej-simon

@ondrej-simon ondrej-simon commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Proof of concept for #25145: remove unused and duplicate imports from all generated Java files.

Draft / interim PR to gather feedback on the approach before polishing it.

How it works

  • New hook CodegenConfig#postProcessTemplateOutput(String content, File target) (default: returns the content unchanged), applied in TemplateManager.write to the rendered template output before it is written. This keeps minimalUpdate and skipOverwrite working, and it applies to models, APIs and supporting files alike, including user-provided custom templates. Other generators (e.g. Kotlin) could reuse the hook later.

  • AbstractJavaCodegen uses it for .java files with a new, dependency-free JavaImportPruner. It is applied unconditionally (no option), as unused/duplicate imports are never desired.

  • JavaImportPruner is a lexical pruner:

    • comments, string/char literals and text blocks are masked;
    • an import is used when its simple name occurs as an unqualified identifier in the code (so java.util.Locale.ROOT does not count as a use of an imported Locale);
    • unused single-type and static imports and duplicate imports are removed;
    • wildcard imports and imports referenced from Javadoc ({@link}, @see, @throws, ...) are kept;
    • imports clashing with a top-level type declared in the same file are removed (they cannot compile, see [BUG][JAVA] Unused Locale Imports #22313, [BUG] [JAVA] Imports in generated models ignore annotationLibrary option #21991);
    • blank lines left behind in the import section are collapsed.

    Removing an import whose simple name is not used cannot change the meaning of the code, since a single-type import only introduces that simple name.

Impact

Regenerating all 826 sample configs changes only .java files: 7467 files, 43188 lines removed, 0 added, across 235 sample projects (Java clients, Spring, JAX-RS, Helidon, Micronaut, Camel, ...). No other sample changes.

Example (samples/client/petstore/java/restclient-springBoot4-jackson3/.../api/PetApi.java):

 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
-import java.util.Arrays;
-import java.util.stream.Collectors;
-import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.ParameterizedTypeReference;

Tests

  • JavaImportPrunerTest: unit tests for the pruner (comments/literals/text blocks, static and wildcard imports, Javadoc references, qualified usages, duplicates, type name clashes, nested types, blank lines).
  • JavaClientCodegenTest#shouldRemoveUnusedImports (restclient, webclient) and SpringCodegenTest#shouldRemoveUnusedImports.
  • A few existing tests asserted imports that the generated code does not use; they are adapted:
    • testJspecify*: now assert org.jspecify.annotations.Nullable is imported if and only if @Nullable is used.
    • addsImportForSetResponse (jaxrs-spec): Set was only used in a string (responseContainer = "Set"); the test now generates interfaceOnly without Response, where the method returns Set<String>.
    • annotationLibrary* ([BUG] [JAVA] Imports in generated models ignore annotationLibrary option #21991): the swagger2 Schema import clashes with the model named Schema and is now removed.
    • shouldAddValidAnnotationIntoCollectionWhenBeanValidationIsEnabled_issue17150, testJdkHttpClientWithJackson3: asserted imports which are not used.
    • supportsJackson3WithOpenApiNullableForSpringBoot4Libraries: now asserts JsonNullableJackson3Module is actually registered. This uncovered a bug: with resttemplate (Spring Boot 4, Jackson 3, openApiNullable=true), the module is only imported but registered for XML only (withXml), not for JSON. The test previously passed only because of the unused import. Marked with a FIXME for now, to be fixed separately.

Verified locally: all tests of the generator module pass (except the C++ Boost runtime tests, which need a local Boost installation), and the pruned Spring Boot 4 spring/restclient/webclient, sealed oneOf, okhttp-gson, native, jersey3, feign, microprofile, jaxrs-spec, helidon and apache-httpclient samples compile. I also tried it in a real-world project via the Maven plugin.

Open questions

  1. Is the CodegenConfig/TemplateManager hook acceptable, or is there a preferred place?
  2. OK to apply it unconditionally, or should there be an opt-out?

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

cc @wing328, Java technical committee: @bbdouglas @sreeshas @jfiala @lukoyanov @cbornet @jeff9finger @karismann @Zomzog @lwlee2608 @martin-mfg @KannaKim, Java Spring technical committee: @cachescrubber @welshm @MelleD @atextor @manedev79 @javisst @borsch @banlevente


Summary by cubic

Removes unused and duplicate imports from all generated Java files, addressing #25145.

  • Adds a CodegenConfig#postProcessTemplateOutput hook applied to rendered template output in TemplateManager before writing, so it works with minimalUpdate, skipOverwrite, and custom templates; AbstractJavaCodegen uses it for all .java files unconditionally via the new JavaImportPruner.
  • JavaImportPruner is a dependency-free lexical pruner: it masks comments, string/char literals, and text blocks; removes imports whose simple name never appears as an unqualified identifier; keeps wildcard imports and imports referenced from Javadoc; removes imports clashing with a top-level type declared in the same file; and collapses blank lines left in the import section.
  • Regenerating all 826 sample configs changes only .java files: 7,467 files, 43,188 lines removed, 0 added.

Bug uncovered

  • With Spring Boot 4, Jackson 3, openApiNullable=true, and the resttemplate library, JsonNullableJackson3Module is imported but only registered for XML, not JSON; the existing test passed only because of the unused import. Marked with a FIXME, to be fixed separately.

Written for commit 3fb1678. Summary will update on new commits.

Review in cubic

Add a CodegenConfig#postProcessTemplateOutput hook, applied to the rendered
template output in TemplateManager before it is written, and use it in
AbstractJavaCodegen to remove unused and duplicate imports from all
generated Java files (JavaImportPruner). Adapt tests which asserted
imports that are not used by the generated code.

See OpenAPITools#25145
Regenerate samples: unused and duplicate imports are removed from the
generated Java files.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant