Repository navigation
[REQ][Java] Remove unused imports from generated Java code #25145
Description
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:
- [BUG][JAVA] Unused
LocaleImports #22313: an unusedimport java.util.Localeclashes with a model namedLocaleand breaks compilation. - [BUG] [JAVA] Unused Import Requires Its Own Dependency #18289: an unused import requires an additional dependency just to compile.
- [BUG] [spring] [server] 5.0.1 unused imports in generated interfaces #8776, [BUG] Unused JsonNullable imports #16931, [BUG][JAVA] Generating native Java models with AllOf/OneOf definitions without creating client creates a model with unused imports #23700, [BUG][JAVA] Unused
LocaleImports #22313 were all reported/fixed one import at a time.
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; // <-- unusedsamples/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; // <-- unusedsamples/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; // <-- unusedWhere they come from. Imports reach a generated file from two sources:
{{#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")inAbstractJavaCodegen, orNullableadded to every Spring model. For APIs it is the union over all operations of a tag.- Hard-coded
importlines in the templates, mostly unconditional or only guarded by coarse flags (e.g. all swagger2 annotations inJavaSpring/api.mustache,Locale/Objects/Arrays/Collectors/FileSystemResourcein the restclient/webclientapi.mustache,URI/OffsetDateTimeinJavaSpring/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 inDefaultCodegen), called inTemplateManager.writebetweencompileTemplateandwriteToFile. Doing it there keepsminimalUpdateandskipOverwriteworking, and it applies to models, APIs and supporting files alike, including user-provided custom templates. - A small dependency-free import pruner used by
AbstractJavaCodegenfor.javatargets, 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
LocaleImports #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.Localein 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 useExampleObject"), 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 withminimalUpdate/skipOverwrite). - JavaParser-based pruning. JavaParser is already a dependency, but the current version (3.24.9) cannot parse
sealed/permits(generated byuseSealed), 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:
- Is a new
CodegenConfighook inTemplateManager.writeacceptable, or is there a preferred place? - 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)?
- 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
wing328 commentedon Oct 6, 2026
another way is to use spotless: https://github.com/diffplug/spotless/blob/main/lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java
[Java] Remove unused and duplicate imports from generated code
ondrej-simon commentedon Oct 6, 2026
Thanks @wing328, Spotless is a good idea! It is actually already used in some of the generated projects: the
okhttp-gson,native,jersey2andjersey3pom.xml/build.gradletemplates configure Spotless withremoveUnusedImports. It is not bound to a lifecycle phase though, so it only applies when runningspotless:applyon 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:
Using it inside the generator itself seems harder:
RemoveUnusedImportsStepresolves google-java-format (or cleanthat) at runtime through a SpotlessProvisioner, and current google-java-format requires Java 21 and--add-exportsforjdk.compilerinternals 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
.javafiles, 43188 lines removed, nothing else changes), the pruned samples I checked compile, and it already uncovered one bug (JsonNullableJackson3Modulenot registered for JSON withresttemplate). Feedback on the approach is very welcome!Picazsoo commentedon Oct 12, 2026
I personally think this should be left to the linting tooling such as spotless. I do not understand the need for out-of-the-box spotless generated code. The value of the open api generator lies IMO elsewhere. And I fear that integrating such tooling earlier in the process might interfere with stuff such as custom side-loaded mustache templates, etc...
ondrej-simon commentedon Oct 13, 2026
Thanks @Picazsoo, fair concern. I agree Spotless is a good fit, and the generator already partly relies on it: for example okhttp-gson, native,... ship Spotless with removeUnusedImports in their pom.xml/build.gradle. The gap is that it only applies when the generated project is built on its own with spotless:apply. It doesn't apply in the common case where code is generated into target/generated-sources via the Maven/Gradle plugin.
As @wing328 suggested, I tried the Spotless route first. As an external step in a consuming project (the spotless-maven-plugin config above, bound to process-sources), it works well and is a valid workaround today. Running it inside the generator was harder. RemoveUnusedImportsStep loads google-java-format at runtime, which needs Java 21 and --add-exports for jdk.compiler internals. So #25157 uses a small pruner with no dependencies instead, with the same goal and result.
On custom templates: the pruner runs on the final rendered output, so it treats built-in and custom templates the same way. It removes an import only if that name never appears unqualified in the code. Such an import has no effect on the compiled code, so removing it can't change behaviour. If it can't tell whether an import is used, it keeps it (wildcards, Javadoc references, etc.). JAVA_POST_PROCESS_FILE and any Spotless/formatter setup still run afterwards as before.
That said, I'm happy to make it configurable, e.g. a removeUnusedImports option, so anyone who'd rather leave this to their own linting can turn it off. Would that address your concern? @wing328 any preference on the default?
Picazsoo commentedon Oct 13, 2026
Good - I will admit I did not look at your PR.
As you can probably tell, I am not a maintainer of this repo - just another person with loud opinions (-:
So I will excuse myself from this discussion going forward - your explanation about in which step the code is cleaned resolved my worries. Thanks for taking the time!