Skip to content

[REQ][Java] Remove unused imports from generated Java code #25145

Description

@ondrej-simon

Is your feature request related to a problem? Please describe.

Generated Java code very often contains unused (and sometimes duplicated) imports. Apart from being a code smell that IDEs and linters flag in every generated file, it has caused real problems:

To get an idea of the scale, I ran a simple analysis over the committed samples (an import counts as unused when its simple name does not occur as an identifier in the code, outside of comments and string literals):

generator files files with ≥1 unused import unused imports duplicate imports
spring (71 sample dirs) 2483 2329 (94%) 13240 484
java (105 sample dirs, all libraries) 4824 3368 (70%) 18555 3503
java-camel 41 22 108 0

Examples from the current samples:

samples/client/petstore/java/restclient-springBoot4-jackson3/.../api/PetApi.java
import java.util.HashMap;
import java.util.List;
import java.util.Locale;   // <-- unused
import java.util.Map;
import java.util.Objects;   // <-- unused
import java.util.Arrays;   // <-- unused
import java.util.stream.Collectors;   // <-- unused
import org.springframework.core.io.FileSystemResource;   // <-- unused
samples/client/petstore/java/restclient-springBoot4-jackson3/.../model/Pet.java
import java.util.Objects;
import java.util.Arrays;   // <-- duplicate
...
import java.util.ArrayList;
import java.util.Arrays;   // <-- duplicate
...
import com.fasterxml.jackson.annotation.JsonTypeName;   // <-- unused
import com.fasterxml.jackson.annotation.JsonIgnore;   // <-- unused
samples/openapi3/server/petstore/springboot-4/.../model/Category.java
import java.net.URI;   // <-- unused
import com.fasterxml.jackson.annotation.JsonCreator;   // <-- unused
import java.time.OffsetDateTime;   // <-- unused
import jakarta.validation.Valid;   // <-- unused
import tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;   // <-- unused

Where they come from. Imports reach a generated file from two sources:

  1. {{#imports}}import {{import}};{{/imports}}, computed by the codegen (fromModel/fromOperation/postProcessModelProperty, ...). Some are added unconditionally, e.g. importMapping.put("com.fasterxml.jackson.annotation.JsonProperty", "com.fasterxml.jackson.annotation.JsonCreator") in AbstractJavaCodegen, or Nullable added to every Spring model. For APIs it is the union over all operations of a tag.
  2. Hard-coded import lines in the templates, mostly unconditional or only guarded by coarse flags (e.g. all swagger2 annotations in JavaSpring/api.mustache, Locale/Objects/Arrays/Collectors/FileSystemResource in the restclient/webclient api.mustache, URI/OffsetDateTime in JavaSpring/model.mustache).

For the Spring Boot 4 spring, restclient and webclient samples, 480 of 494 unused imports come from hard-coded template lines (2.), only 14 from the computed imports (1.). Duplicates appear where both sources import the same class.

Describe the solution you'd like

A generic, opt-in post-processing step that removes unused imports from generated Java sources, applied to the rendered template output before it is written to disk:

  • A new hook in CodegenConfig, e.g. String postProcessTemplateOutput(String content, File target) (identity by default in DefaultCodegen), called in TemplateManager.write between compileTemplate and writeToFile. Doing it there keeps minimalUpdate and skipOverwrite working, and it applies to models, APIs and supporting files alike, including user-provided custom templates.
  • A small dependency-free import pruner used by AbstractJavaCodegen for .java targets, enabled by a new option (e.g. removeUnusedImports):
    • removes single-type and static imports whose simple name never occurs as an identifier in the code (comments and string literals stripped), and duplicate imports;
    • keeps wildcard imports and imports only referenced from Javadoc (e.g. {@link Foo});
    • optionally also drops imports whose simple name equals a type declared in the same file (always a compile error, see [BUG][JAVA] Unused Locale Imports #22313).

This is safe for compilation by construction: a single-type import only introduces its simple name, so if the name is not used, removing the import cannot change the meaning of the code. As a sanity check, I applied this logic to copies of the springboot-4, springboot-4-jspecify-useOptional, restclient-springBoot4-jackson3 and webclient-springBoot4-jackson3 samples: 285 imports were removed, and all of them still compile.

Possible rollout: introduce the option disabled by default (enabled e.g. in the Spring Boot 4 spring/restclient/webclient sample configs) to keep the sample diff reviewable, and enable it by default later, e.g. in a minor release. Other generators (e.g. Kotlin) could reuse the same hook with their own pruner later.

Describe alternatives you've considered

  • Fixing templates/codegen import by import (what has been done so far, e.g. [Java] Use Fully Qualified Name for java.util.Locale in Generated Classes #22342). It doesn't scale: many imports would need per-file "is it used" conditions (e.g. "does any operation of this tag use ExampleObject"), across many templates/libraries, it regresses easily, and doesn't help custom templates. Still worth doing for the obviously wrong codegen-side imports, but as a complement.
  • External formatter via JAVA_POST_PROCESS_FILE (e.g. google-java-format --fix-imports-only). Already possible, but opt-in, requires external tooling, spawns a process per file, and runs after the file is written (so it doesn't play well with minimalUpdate/skipOverwrite).
  • JavaParser-based pruning. JavaParser is already a dependency, but the current version (3.24.9) cannot parse sealed/permits (generated by useSealed), and re-printing the AST risks formatting changes. A lexical approach is simpler and sufficient for this purpose.

Additional context

Before working on a PR, I'd like to know whether this is something the maintainers would be interested in, and if so:

  1. Is a new CodegenConfig hook in TemplateManager.write acceptable, or is there a preferred place?
  2. Should the option be opt-in first (and on by default later), or on by default right away (large sample diff across all Java generators)?
  3. Any preference on the option name (removeUnusedImports)?

I'm happy to implement it, starting with the Spring Boot 4 spring, restclient and webclient generators.

Activity

  1. ondrej-simon commented on Oct 6, 2026

    @ondrej-simon
    ContributorAuthor

    Thanks @wing328, Spotless is a good idea! It is actually already used in some of the generated projects: the okhttp-gson, native, jersey2 and jersey3 pom.xml/build.gradle templates configure Spotless with removeUnusedImports. It is not bound to a lifecycle phase though, so it only applies when running spotless:apply on the standalone generated project, not when the code is generated into an existing project (e.g. via the Maven plugin).

    I tried it as a workaround in a consuming project, and it works well:

    <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <version>3.10.3</version>
        <configuration>
            <java>
                <includes>
                    <include>target/generated-sources/openapi/src/main/java/**/*.java</include>
                </includes>
                <removeUnusedImports/>
            </java>
        </configuration>
        <executions>
            <execution>
                <phase>process-sources</phase>
                <goals><goal>apply</goal></goals>
            </execution>
        </executions>
    </plugin>

    Using it inside the generator itself seems harder: RemoveUnusedImportsStep resolves google-java-format (or cleanthat) at runtime through a Spotless Provisioner, and current google-java-format requires Java 21 and --add-exports for jdk.compiler internals when run in-process.

    So I drafted a proof of concept with a small dependency-free import pruner, applied to every generated Java file before it is written: #25157 (draft). Regenerating all samples only removes import lines (7467 .java files, 43188 lines removed, nothing else changes), the pruned samples I checked compile, and it already uncovered one bug (JsonNullableJackson3Module not registered for JSON with resttemplate). Feedback on the approach is very welcome!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions