IDE hook crashes Maven 4
I am looking at developing a VSCode plugin for Spotless that works with Maven. For performance reasons I want to use mvnd which is a daemon-based version of Maven, and which is based on Maven 4.x.
I have found that on Maven 4, if I invoke:
$ mvn spotless:apply -DspotlessIdeHook=/home/jay/Projects/vscode-spotless-maven/test-project/src/main/java/com/mycompany/app/App.java
then it executes successfully but crashes Maven while cleaning up:
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------------------------------< com.mycompany.app:my-app >-----------------------------------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] ---------------------------------------------------------[ jar ]----------------------------------------------------------
[INFO]
[INFO] --- spotless:2.46.1:apply (default-cli) @ my-app ---
[WARNING] [stderr] IS CLEAN
[INFO] --------------------------------------------------------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------------------------------------------------------------------------
[INFO] Total time: 0.802 s
[INFO] Finished at: 2025-08-02T16:33:13+01:00
[INFO] --------------------------------------------------------------------------------------------------------------------------
[ERROR] Unable to close context
org.apache.maven.api.cli.InvokerException: Unable to close context
at org.apache.maven.cling.invoker.LookupContext.close(LookupContext.java:128)
at org.apache.maven.cling.invoker.LookupInvoker.invoke(LookupInvoker.java:143)
at org.apache.maven.cling.ClingSupport.run(ClingSupport.java:76)
at org.apache.maven.cling.MavenCling.main(MavenCling.java:51)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:255)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:201)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:361)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:314)
Suppressed: java.lang.NullPointerException: Cannot invoke "org.apache.maven.logging.LoggingOutputStream.forceFlush()" because "this.out" is null
at org.apache.maven.logging.LoggingOutputStream$LoggingPrintStream.forceFlush(LoggingOutputStream.java:88)
at org.apache.maven.logging.LoggingOutputStream.forceFlush(LoggingOutputStream.java:94)
at org.apache.maven.cling.invoker.LookupInvoker.lambda$doConfigureWithTerminalWithRawStreamsDisabled$3(LookupInvoker.java:378)
at org.apache.maven.cling.invoker.LookupContext.close(LookupContext.java:118)
... 9 more
Suppressed: java.lang.NullPointerException: Cannot invoke "org.apache.maven.logging.LoggingOutputStream.forceFlush()" because "this.out" is null
at org.apache.maven.logging.LoggingOutputStream$LoggingPrintStream.forceFlush(LoggingOutputStream.java:88)
at org.apache.maven.logging.LoggingOutputStream.forceFlush(LoggingOutputStream.java:94)
at org.apache.maven.cling.invoker.LookupInvoker.lambda$doConfigureWithTerminalWithRawStreamsDisabled$1(LookupInvoker.java:376)
at org.apache.maven.cling.invoker.LookupContext.close(LookupContext.java:118)
... 9 more
Note that Spotless doesn't crash Maven if you don't use the IDE hook.
I have been able to narrow this down to the following code in the Maven IDE hook:
https://github.com/diffplug/spotless/blob/7d5c826973a900713fe7b23ed20113b0c2c2315e/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java#L69-L72
I believe this code is the cause because if I make my own minimal mojo on Maven 4.x which looks like this:
package sample.plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo
{
@Override
public void execute() throws MojoExecutionException
{
getLog().info("Hello, world.");
System.err.close();
System.out.close();
}
}
then I am able to reproduce the same crash with mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi. If I then comment those two lines of code out, the crash disappears.
I wanted to ask why we need to close stdout/stderr after the IDE hook has finished? It might well be that this is a Maven issue rather than a Spotless issue (it doesn't feel like a plugin should be able to crash the host build system) but if we can do a quick fix here rather than waiting for a change in Maven that would be great.
Issue template
spotless version: 2.46.1 maven version: Apache Maven 4.0.0-rc-4 (bed0f8174bf728978f86fac533aa38a9511f3872) repro: https://github.com/SapiensAnatis/vscode-spotless-maven/tree/master/test-project (but any Spotless project will work fine if my understanding of the cause is correct)
Great detective work!
why we need to close stdout/stderr after the IDE hook has finished?
My quick search through the history leads me to believe it was like that when it was introduced, and can probably be removed. If you do a local test which shows that removing this fixes it, I'll take your word for it and merge a PR that removes the closing.
Thanks for checking the history @nedtwigg -- I probably could have benefitted from doing the same.
I will start a fork of the plugin and use that in the local development of my extension to check if that fixes my issues. If it does I will be happy to submit a PR.
Removing those lines does fix the crash, but I'll keep working on the plugin and double-check it doesn't regress the existing IDE plugin on Maven 3.
A note for anyone else looking at Maven 4 and the IDE hook -- it now decorates stdout with prefixes at the start of each line. Per https://github.com/apache/maven/issues/2523 this can be disabled by passing --raw-streams.
I forgot about this for a bit, there are still some other problems I'm having with getting everything working with mvnd (I feel like we are using the stdin facilities a bit strangely so I can't really blame them). I don't really see a point in putting a PR up and validating it until we can show it delivers benefit by enabling usage with mvnd end to end.