You should probably do this using JMX, as I mentioned in my comment.
JMX allows you to expose "management beans" from you program that can then be invoked remotely.
Lets start with a simple example:
private static class Consumer implements Runnable {
private final BlockingQueue<String> blockingQueue;
public Consumer(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
try {
final String s = blockingQueue.take();
System.out.println(s);
if ("EXIT".equalsIgnoreCase(s)) {
return;
}
} catch (InterruptedException ex) {
return;
}
}
}
}
public static interface ProducerMBean {
void add(String string);
String contents();
}
private static class Producer implements ProducerMBean {
private final BlockingQueue<String> blockingQueue;
public Producer(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void add(String string) {
blockingQueue.add(string);
}
@Override
public String contents() {
return blockingQueue.toString();
}
}
public static void main(String[] args) throws Exception {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
final BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
executorService.submit(new Consumer(blockingQueue));
final Producer producer = new Producer(blockingQueue);
final ObjectName name = new ObjectName("com.example.producer:type=SomeUnqiueName");
ManagementFactory.getPlatformMBeanServer().registerMBean((ProducerMBean) producer, name);
executorService.shutdown();
}
This is a producer/consumer pattern with a twist.
The consumer is perfectly boring, it spits out String
s from the queue and bails out if its interrupted or receives the String
"EXIT". The producer on the other hand is a little more interesting.
First we have the interface ProducerMBean
, this is a public interface
and acts as the outward facing point of interaction as we shall see later. We then have a Producer
which implements ProducerMBean
, it simply adds things to the BlockingQueue
. It's worth noting here that all this must be thread-safe otherwise you will have issues with the JMX threads poking your methods asynchronously to you application threads.
We then register our MBean (management bean) with the platform, this exposes the interface
to the outside would; we need to be careful what we expose as anything we expose can be called. The format for the ObjectName
is <folder>:type=<name>
, you will see what I mean later.
So now, when the application is started it just hangs, as expected. In order to send Strings
we need to open jconsole and hook into the PID

The <folder>
part of the ObjectName
becomes the top level directory and the <name>
part of the ObjectName
becomes the MBean name itself. You can now use the add
method to send the app String
s and see it print them. Nifty, but how do we call that from the commandline?
First we need to register the MBean server to a port rather than a PID, this is done by invoking the application with the following JVM args:
-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote=true
Now if you run jconsole localhost:<port>
you will connect directly to the application's MBean server. Magic.
This still doesn't quite do what we want. There is a SO answer that deals with commandline access to a JMX server, but I prefer to simply use another, helper, app.
This app would start, read all the arguments from the commandline, then invoke then MBean method specified with the arguments given.
Here is a simple example
public static void main(String[] args) throws Exception {
final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:<port>/jmxrmi");
final JMXConnector jmxc = JMXConnectorFactory.connect(url);
final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
final ObjectName name = new ObjectName("com.example.producer:type=SomeUnqiueName");
final ProducerMBean mbeanProxy = JMX.newMBeanProxy(mbsc, name, ProducerMBean.class, true);
mbeanProxy.add("TEST");
mbeanProxy.add("EXIT");
}
This application needs to have the interface ProducerMBean
on the classpath, the easiest way is to run the helper with the main application on the classpath. We connect to the JMX server running on localhost:<port>
and then get the MBean with the same name that we registered earlier.
We can now invoke methods on that MBean, sending commands to the other application.
Obviously you need to rewrite the helper app to read the commandline arguments, you could then invoke something like
java -cp MyMainApp.jar:MyHelperApp.jar com.example.helper.Main add TEST EXIT
From you script to pass in arguments to the main Java program.