You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[REQ][Java] Remove unused imports from generated Java code #25145
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):
Where 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") in AbstractJavaCodegen, or Nullable added to every Spring model. For APIs it is the union over all operations of a tag.
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});
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:
Is a new CodegenConfig hook in TemplateManager.write acceptable, 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.
Thanks @wing328, Spotless is a good idea! It is actually already used in some of the generated projects: the okhttp-gson, native, jersey2 and jersey3pom.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:
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!
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:
LocaleImports #22313: an unusedimport java.util.Localeclashes with a model namedLocaleand breaks compilation.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):
spring(71 sample dirs)java(105 sample dirs, all libraries)java-camelExamples from the current samples:
samples/client/petstore/java/restclient-springBoot4-jackson3/.../api/PetApi.javasamples/client/petstore/java/restclient-springBoot4-jackson3/.../model/Pet.javasamples/openapi3/server/petstore/springboot-4/.../model/Category.javaWhere 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.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,restclientandwebclientsamples, 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:
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.AbstractJavaCodegenfor.javatargets, enabled by a new option (e.g.removeUnusedImports):{@link Foo});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-jackson3andwebclient-springBoot4-jackson3samples: 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/webclientsample 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
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.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).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:
CodegenConfighook inTemplateManager.writeacceptable, or is there a preferred place?removeUnusedImports)?I'm happy to implement it, starting with the Spring Boot 4
spring,restclientandwebclientgenerators.