Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java sub-process command line execution with/without cmd.exe

My question consists of several things I don't understand about the use of "cmd.exe", "/c" when executing sub-processes from Java in Windows. Basically, I couldn't find a good explanation about when and why they're needed.

My specific problems: I have a small framework for sub-processes execution. One use is a Java application which "manages" several other JVMs created by ProcessBuilders. One of the key requirements is that when a sub-process is stuck, or the hosting application is terminating, it must be able to kill the sub-processes.
The problem is, on one hand, doing this:

new ProcessBuilder("java", "...").start();

Causes this:

Could not find or load main class ...

As if the system variables or directory are different (which they're not). On the other hand, wrapping it in a cmd.exe like this:

new ProcessBuilder("cmd.exe", "/c", "java", "...").start();

WORKS, but creates another cmd.exe process, which has a side effect: the child JVM is now a sub-sub-process, and process.destroy(); doesn't kill it (a known bug in the Windows JRE as I found).

This specific problem was handled on a different level, since all those applications are ours and we know their PIDs. But it's an example how cmd.exe makes everything work differently (or prevent the JVM from working at all). So I'd like to know what exactly happens there.

Here the framework itself comes into the picture as well. It's also going to be used by our testing platform. I'd like to provide an API which allows wrapping the command with a cmd.exe /c by a parameter. But, what exactly is the meaning of that parameter? How do the users decide if they want a cmd.exe wrapping?

AND a bonus I'd appreciate: is any of this relevant in other OS? Does it have some kind of an equivalent, say, in Linux?

like image 554
Eugene Marin Avatar asked Dec 08 '25 06:12

Eugene Marin


1 Answers

Came across it again and found the problem, and some additional insights.

@HarryJohnston - good point, according to the javadoc the sub-process inherits the environment from both ProcessBuilder and Runtime.getRuntime.exec(...) APIs so it's not the issue (and java.exe is in fact found so PATH is obviously available).

But it seems certain command line features are lost. Among others it's this kind of %VARIABLE% usage in command line - they're not interpreted unless the command starts with cmd.exe /c.

In this case of class not found (it could also cause NoClassDefFoundError), I had several variables used in the classpath (perhaps I should have posted the entire command, but it's kinda long). When I replaced them with the literary paths, everything worked.

Basically:

java -cp %classpath% Main - bad

cmd.exe /c java -cp %classpath% Main - good

java -cp D:\proj\bin Main - good

So, on the general question of how's the behavior different with cmd.exe:

  1. Variable references like this: %VARIABLE% are only interpreted by cmd.exe.
  2. With cmd.exe you don't really need to split the command into String array of parameters, you can use one long String (array of "cmd.exe", "/c", "the rest...").
  3. The sub-process will be cmd.exe and not the actual executable, so process.destroy() won't kill it. This might be a bug fixed in later versions (I used Java 7 and 8, Windows 7 and Server 2012).
  4. File associations only work with cmd.exe, so you can't "execute" a file that is not a program or script ("CreateProcess error=193, %1 is not a valid Win32 application"). Calling executables defined in PATH does work.
  5. start and call are only available under cmd.exe.
  6. Command level errors behave differently as well: With cmd.exe the error will just be in the output stream, whereas without it the API throws an exception, which might also have a slightly different description.

    For example:

    nothing will cause java.io.IOException: CreateProcess error=2, The system cannot find the file specified

    cmd.exe /c nothing will return output: 'nothing' is not recognized as an internal or external command, operable program or batch file, and a return value of 1 (the only indication something went wrong).

On the deeper question of why, and is this the full list of differences, I still don't really know. Looks like the JVM has its way of running commands, which you can "wrap" with cmd.exe for additional features and protection.

Another interesting fact is when you run a Batch, it's actually run under a cmd.exe (with all its features). From my tests it seems process.destroy() can only kill it if it's not waiting for anything external (if stuck on pause for example), with or without additional cmd.exe /c wrapping, but not if it runs a sub-process - which is consistent with the cmd.exe limitation mentioned in point 3.

like image 174
Eugene Marin Avatar answered Dec 09 '25 18:12

Eugene Marin