Can only read 1 byte from stdin
Affected version
Apache Maven Daemon (mvnd) 2.0.0-rc-4-SNAPSHOT linux-amd64 native client (6734804eedc68f0f30aca315e842f3ec015def73)
Terminal: org.jline.terminal.impl.PosixSysTerminal with pty org.jline.terminal.impl.jni.linux.LinuxNativePty
Apache Maven 4.0.0-rc-4 (bed0f8174bf728978f86fac533aa38a9511f3872)
Maven home: /home/jay/Projects/maven-mvnd/dist/target/maven-mvnd-2.0.0-rc-4-SNAPSHOT-linux-amd64/mvn
Java version: 24.0.2, vendor: GraalVM Community, runtime: /home/jay/.local/jvm/jdk24-graalvm
Default locale: en_GB, platform encoding: UTF-8
OS name: "linux", version: "6.17.0-2-default", arch: "amd64", family: "unix"
Bug description
I am looking to use mvnd as a way to run an auto-formatter in an IDE plugin, so far it looks much faster than mvn so I'm very keen to get it working.
The auto-formatter mojo tries to read the document from stdin. I have noticed this works fine in mvn but on mvnd it is only able to read 1 byte. I made the following mojo to demonstrate the issue:
package sample.plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import java.nio.charset.StandardCharsets;
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException {
byte[] bytes = new byte[] {};
try {
System.out.println("Available bytes: " + System.in.available());
bytes = System.in.readAllBytes();
} catch (Exception ex) {
getLog().error("Failed to read from stdin", ex);
}
System.out.println("Read " + bytes.length + " bytes from stdin.");
System.out.println("Stdin content: \"" + new String(bytes, StandardCharsets.UTF_8) + "\"");
}
}
If I run this with mvn as follows:
$ echo -n "Hello" | mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi
then I get the expected output:
[INFO] --- hello:1.0-SNAPSHOT:sayhi (default-cli) @ hello-maven-plugin ---
[INFO] [stdout] Available bytes: 5
[INFO] [stdout] Read 5 bytes from stdin.
[INFO] [stdout] Stdin content: "Hello"
However if I use mvnd:
$ echo -n "Hello" | mvnd sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi
then I get this output:
[INFO] --- hello:1.0-SNAPSHOT:sayhi (default-cli) @ hello-maven-plugin ---
[INFO] [stdout] Available bytes: 0
[INFO] [stdout] Read 1 bytes from stdin.
[INFO] [stdout] Stdin content: "H"
or sometimes I get [INFO] [stdout] Stdin content: "". I have found if I ignore System.in.available() and keep trying to read 1 character at a time I can eventually get the whole stdin content, but it would be nice if readAllBytes just worked.
Could this be a delay in the daemon receiving the standard input from the terminal interface? I did try adding a sleep for 1s at the start of my mojo but this didn't change the behaviour at all.
In an attempt to be helpful I thought I would look at the code and see what I could find, I could be completely wrong but I suspect the cause may be sending an EOF before sending the text in TerminalInputHandler::handleProjectInput. I made this change to add some debug logging and move the EOF to after the response:
index c4071cbe..74f0aa62 100644
--- a/common/src/main/java/org/mvndaemon/mvnd/common/logging/TerminalInputHandler.java
+++ b/common/src/main/java/org/mvndaemon/mvnd/common/logging/TerminalInputHandler.java
@@ -127,6 +127,8 @@ public class TerminalInputHandler implements AutoCloseable {
}
private void handleProjectInput(String projectId, int bytesToRead) throws IOException {
+ System.err.println("Project " + projectId + " requests to read " + bytesToRead + " bytes.");
+
if (daemonReceive == null) {
return;
}
@@ -138,17 +140,22 @@ public class TerminalInputHandler implements AutoCloseable {
int c = terminal.reader().read(timeout);
if (c < 0) {
// End of stream reached
- daemonReceive.accept(Message.inputEof());
+ System.out.println("Reached end of stream");
break;
}
+
+ System.out.println("Read character: " + (char)c);
buf[idx++] = (char) c;
timeout = idx > 0 ? 1 : 10; // Shorter timeout after first char
}
if (idx > 0) {
String data = String.valueOf(buf, 0, idx);
+ System.out.println("Sending response: " + data);
daemonReceive.accept(Message.inputResponse(data));
}
+
+ daemonReceive.accept(Message.inputEof());
}
private void handleControlKeys() throws IOException {
then my example sort of works, but I have to remove my call to System.in.available(). It comes through like this:
[INFO] --- hello:1.0-SNAPSHOT:sayhi (default-cli) @ hello-maven-plugin ---
Project hello-maven-plugin requests to read 16384 bytes.
Read character: H
Read character: e
Read character: l
Read character: l
Read character: o
Reached end of stream
Sending response: Hello
Project hello-maven-plugin requests to read 16379 bytes.
Reached end of stream
[INFO] [stdout] Read 5 bytes from stdin.
[INFO] [stdout] Stdin content: "Hello"
If I keep the System.in.available() it seems like that comes through as a request to read 1 byte, which breaks under both master and my change:
[INFO] --- hello:1.0-SNAPSHOT:sayhi (default-cli) @ hello-maven-plugin ---
Project hello-maven-plugin requests to read 1 bytes.
Read character: H
Sending response: H
[INFO] [stdout] Available bytes: 0
Project hello-maven-plugin requests to read 16383 bytes.
[INFO] [stdout] Read 1 bytes from stdin.
[INFO] [stdout] Stdin content: "H"
[INFO] ----------------------------------------------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ----------------------------------------------------------------------------------------------------------------
[INFO] Total time: 0.044 s (Wall Clock)
[INFO] Finished at: 2025-10-12T18:05:50+01:00
[INFO] ----------------------------------------------------------------------------------------------------------------
Read character: e
Read character: l
Read character: l
Read character: o
Reached end of stream
Sending response: ello
Could you setup a simple reproducer project ?
Yeah sure, I've just pushed up what I have been using to test locally: https://github.com/SapiensAnatis/maven-stdin-issue
You should just be able to use mvn install from the repo root and then use the commands from the post to see the issue.