Skip to content

Add Maven plugin integration for ExpandWildcardImportsStep#3

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/add-maven-plugin-integration-again
Draft

Add Maven plugin integration for ExpandWildcardImportsStep#3
Copilot wants to merge 6 commits intomainfrom
copilot/add-maven-plugin-integration-again

Conversation

Copy link

Copilot AI commented Jan 24, 2026

ExpandWildcardImportsStep was already implemented in lib and Gradle plugin but not available in Maven plugin. The step requires a Collection<File> typeSolverClasspath containing project sources and dependencies for type resolution.

Changes

Configuration chain extended to pass MavenProject:

  • Added MavenProject field to FormatterConfig and FormatterStepConfig
  • Updated FormatterFactory and AbstractSpotlessMojo to propagate project reference

Created ExpandWildcardImports FormatterStepFactory:

  • Collects all compile and test source roots via getCompileSourceRoots() and getTestCompileSourceRoots()
  • Gathers project artifacts for dependency resolution
  • Constructs typeSolverClasspath and delegates to existing ExpandWildcardImportsStep.create()

Integration:

  • Added addExpandWildcardImports() method to Java formatter
  • Documented usage in Maven plugin README
  • Created integration tests including:
    • Single module test with wildcard import expansion
    • Multi-module test validating cross-module dependency resolution

Usage

CODE_BLOCK_0_0

Transforms:CODE_BLOCK_0_1

Into:CODE_BLOCK_0_2

Testing

The implementation is validated with comprehensive integration tests:

  • Single module test: Verifies wildcard import expansion within a single Maven project
  • Multi-module test: Creates a project with common and app modules where app depends on common, ensuring that wildcard imports are correctly expanded across module boundaries and inter-module dependencies are properly resolved

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • archive.eclipse.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:MaxMetaspaceSize=1024m -Xmx4g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
  • java.sun.com
    • Triggering command: /opt/hostedtoolcache/CodeQL/2.23.9/x64/codeql/tools/linux64/java/bin/java /opt/hostedtoolcache/CodeQL/2.23.9/x64/codeql/tools/linux64/java/bin/java -jar /opt/hostedtoolcache/CodeQL/2.23.9/x64/codeql/xml/tools/xml-extractor.jar --fileList=/home/REDACTED/work/spotless/.codeql-scratch/dbs/java/working/files-to-index2995331916609997450.list --sourceArchiveDir=/home/REDACTED/work/spotless/.codeql-scratch/dbs/java/src --outputDir=/home/REDACTED/work/spotless/.codeql-scratch/dbs/java/trap/java (dns block)
  • scans-in.gradle.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:MaxMetaspaceSize=1024m -Xmx4g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
      >
      > If you need me to access, download, or install something from one of these locations, you can either:
      >
      > - Configure Actions setup steps to set up my environment, which run before the firewall is enabled
      > - Add the appropriate URLs or hosts to the custom allowlist in this repository's Copilot coding agent settings (admins only)
      >
      >

typeSolverClasspath to pass to java parser, so we need to create that using information from maven project and pass it to the the step

Original prompt

Fix diffplug#2829

  • ExpandWildcardImportsStep is already implemented in lib/src/main/java/com/diffplug/spotless/java/ExpandWildcardImportsStep.java and integrated with gradle plugin
  • we want to implement maven plugin integration for the same
  • the main challenge is that ExpandWildcardImportsStep needs a Collection typeSolverClasspath to pass to java parser, so we need to create that using information from maven project and pass it to the the step

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits January 24, 2026 04:00
Co-authored-by: shitikanth <544054+shitikanth@users.noreply.github.com>
Co-authored-by: shitikanth <544054+shitikanth@users.noreply.github.com>
…le directories

Co-authored-by: shitikanth <544054+shitikanth@users.noreply.github.com>
Co-authored-by: shitikanth <544054+shitikanth@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Maven plugin integration for ExpandWildcardImportsStep Add Maven plugin integration for ExpandWildcardImportsStep Jan 24, 2026
Copilot AI requested a review from shitikanth January 24, 2026 04:13
@awa-xima
Copy link

awa-xima commented Jan 26, 2026

Some issues I could see when trying it locally (I'm not a Maven expert either):

  • project.getArtifacts() is not guaranteed to include all dependencies. The SpotlessApplyMojo and SpotlessCheckMojo classes have a @Mojo annotation which needs @Mojo(requiresDependencyResolution = ResolutionScope.TEST) for this to work (test scope because test classes may have additional dependencies). But I'm not sure if adding this breaks the Maven plugin in another way.
  • The artifact files are not guaranteed to be JAR files. IDE such as Eclipse and IntelliJ have a Resolve workspace artifacts for multi-module projects so that dependent modules in the current workspace do not have to be built. Then the artifact file does point to the target/classes folder of the referenced module containing the *.class files. The ExpandWildcardsFormatterFunc checks if the file is folder, and if it is, uses a JavaParserTypeSolver. JavaParserTypeSolver requires *.java source files; a JarTypeSolver would need to be used instead.
    • Could be solved e.g. by replacing the Collection<File> of the typeSolverClasspath with a Collection<JavaSource>, where each JavaSource has a flag indicating whether it points to a set of Java source files or a set of compiled class files.

@shitikanth
Copy link
Owner

@copilot add a test with multi-module maven project

Co-authored-by: shitikanth <544054+shitikanth@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 27, 2026

@copilot add a test with multi-module maven project

Added multi-module test in commit 0e5abc2. The test creates a project with:

  • A common module containing shared classes (AnotherClassInSamePackage, AnotherImportedClass, SomeAnnotation)
  • An app module that depends on common and uses wildcard imports
  • Verifies that expandWildcardImports correctly resolves types across module boundaries

@shitikanth
Copy link
Owner

@copilot can you run the test to check if it passes?

@shitikanth
Copy link
Owner

@awa-xima Thanks for the thoughful comments.

Looks like in general we would have to use the DependencyResolver api to support multi module maven projects.

@awa-xima
Copy link

awa-xima commented Jan 27, 2026

Just as a note, we are using spotless in a way that does not work with this approach at all: We have a large multi-module project with 100+ sub modules. Running Maven / spotless on each module would take some time. So we just run Maven on the parent pom and have it format all files in our project. That's fairly fast. But it means that the classpath is not known to Maven. So I think we'll just use the ban wildcard imports step and rely on people configuring their IDE properly.

@shitikanth You're welcome, I'd also like to see it work for Maven. From what little I know from Maven plugins, it's fairly complicated to get it right.

Which DependencyResolver do you mean? I only find classes from Gradle. The AbstractSpotlessMojo already has an injected RepositorySystem. My first try was to use that system to resolve the dependency of the project's artifact (see below). That worked for compile scope dependencies. But it did not pick up test scope dependencies. Only after I added @Mojo(requiresDependencyResolution = ResolutionScope.TEST) to the SpotlessCheckMojo / SpotlessApplyMojo did it pick up those.

But with requiresDependencyResolution, the simpler project.getArtifacts() approach also picked all those dependencies. So that might be enough. And since as I mentioned, the builtin maven-dependency-plugin also sets requiresDependencyResolution = ResolutionScope.TEST, I'm wondering if that's required to make it work properly.

As for multi-module projects, the behavior is also "interesting":

  • Running just mvn spotless:apply, project.getArtifacts() gives the JAR files in the local maven repo for other module in the multi-module project.
  • Running just mvn compile spotless:apply, project.getArtifacts() gives the target/classes files for the other modules.

For reference, my first approach with the dependency resolver (similar to maven-dependency-plugin)

public abstract class AbstractSpotlessMojo extends AbstractMojo {
        // ...

	private Set<JavaSource> resolveClasspathElements() {
		final var classpathElements = new HashSet<JavaSource>();
		try {
			var artifact = project.getArtifact();
			var aetherArtifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), artifact.getVersion(), null, artifact.getFile());
			var collectRequest = new CollectRequest();
			collectRequest.setRepositories(repositories);
			collectRequest.setRoot(new Dependency(aetherArtifact, null));
			var request = new DependencyRequest(collectRequest, null);
			var resolved = repositorySystem.resolveDependencies(repositorySystemSession, request);
			project.getCompileSourceRoots().stream().map(File::new).map(file -> new JavaSource(JavaSourceType.SOURCE_CODE, file)).forEach(classpathElements::add);
			project.getTestCompileSourceRoots().stream().map(File::new).map(file -> new JavaSource(JavaSourceType.SOURCE_CODE, file)).forEach(classpathElements::add);
			resolved.getArtifactResults().stream().map(ArtifactResult::getArtifact).filter(Objects::nonNull).map(Artifact::getFile).filter(Objects::nonNull).map(e -> new JavaSource(JavaSourceType.BYTE_CODE, e)).forEach(classpathElements::add);
			return Set.copyOf(classpathElements);
		} catch (Exception e) {
			throw new PluginException("Unable to resolve project artifacts", e);
		}
	}

@shitikanth
Copy link
Owner

For very large projects, I don't think it is practical (or even necessary) to check the formatting of every single source file. It makes much more sense to run it only on the files changed in current PR (using ratchetFrom=${targetBranch}) or files changed in current commit (if you are running it as part of a git commit hook). Do you think either of those approaches could serve your use case?

I recently contributed a PR that significantly improves the performance of ratchetFrom on large multi-module maven projects, so you might want to reevaluate the need for that sort of a workaround in the first place?

diffplug#2805

I don't think it is a good idea to bypass the build system completely and recreate the dependency resolution logic as part of the plugin.

Which DependencyResolver do you mean?

DependencyResolver is a core maven api that the maven-compiler-plugin and others use to determine what dependencies exist for the project and where they are to be found. See for example

https://github.com/apache/maven-compiler-plugin/blob/master/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java#L1564

@shitikanth
Copy link
Owner

@awa-xima

Running Maven / spotless on each module would take some time

I disagree. It should not take significantly more time to have maven run spotless on each module separately rather than only on the root project. Overhead introduced in initializing the plugin for each sub-module should be small enough that it shouldn't matter. It is a performance bug if that is not the case.

@awa-xima
Copy link

I disagree. It should not take significantly more time to have maven run spotless on each module separately rather than only on the root project. Overhead introduced in initializing the plugin for each sub-module should be small enough that it shouldn't matter. It is a performance bug if that is not the case.

That's my prediction. But I'm willing to give it a try, which is why I'd also like to see it implemented for Maven ; )

Spotless already has an index/cache file . Most of the time, we don't change many files in any particular commit, so spotless doesn't do much. Running mvn spotless:apply -N on the parent pom usually takes about 2-5s, most of which is time spent initializing Java/Maven. Since that's fast enough, we haven't looked much at ratchedFrom yet.

DependencyResolver is a core maven api that the maven-compiler-plugin and others use to determine what dependencies exist for the project and where they are to be found.

I can only find it in the Maven 4.x branch of the maven-compiler-plugin, not in the Maven 3.x branch. org.apache.maven.api.services.DependencyResolver seems to be a newly added API that will be released with Maven 4 (perhaps it might be ok to only support Maven 4?). RepositorySystem might be the equivalent for Maven 3?

@shitikanth
Copy link
Owner

Are you sure that approach is actually buying you any real performance benefits?

I just tried running mvn spotless:apply on https://github.com/aws/aws-sdk-java-v2 with mvn -N spotless:apply using the configuration

    <profile>
        <id>spotless</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>com.diffplug.spotless</groupId>
                    <artifactId>spotless-maven-plugin</artifactId>
                    <version>3.0.0</version>
                    <configuration>
                        <java>
                            <includes>
                                <include>**/*.java</include>
                            </includes>
                            <indent>
                                <spaces>true</spaces>
                                <spacesPerTab>4</spacesPerTab>
                            </indent>
                        </java>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>

(I presume this is sort of what you are doing to apply changes to all source files from the root project).

With the cache primed, this approach takes ~8s, whereas vanilla mvn spotless:apply (same config, minus the includes section - so each module only targets its own source dirs) consistently takes under 4s.

@awa-xima
Copy link

Are you sure that approach is actually buying you any real performance benefits?

Actually no. It just was fast enough so far, so I didn't try another approach. I just tried it, running it once on every module. With the cache enabled it's pretty much just as fast : ) So thanks for bringing it up again, I might not have tried otherwise.

(The unfortunate part is that it looks like people want to introduce lombok and javacompiler understandably can't handle that, but that's another story.)

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.

Add expandWildcardImports feature to the Maven Plugin

3 participants