2

Picocli has to introspect the command tree. Doing so it needs to load the domain object classes for every Command which slows down the jvm startup.

What options are there to avoid this startup lag? One solution I've come up with is described in https://github.com/remkop/picocli/issues/482:

I am using reflection to postpone any class loading until after the command is selected. This way only the command classes themselves are loaded and finally the classes which implement the single command requested by the user:

abstract class BaseCommand implements Runnable {

    interface CommandExecutor {
        Object doExecute() throws Exception;
    }

    // find the CommandExecutor declared at the BaseCommand subclass.
    protected Object executeReflectively() throws Exception {
        Class<?> innerClass = getExecutorInnerClass();
        Constructor<?> ctor = innerClass.getDeclaredConstructor(getClass());
        CommandExecutor exec = (CommandExecutor) ctor.newInstance(this);
        return exec.doExecute();
    }

    private Class<?> getExecutorInnerClass() throws ClassNotFoundException {
        return getClass().getClassLoader().loadClass(getClass().getName() + "$Executor");
    }

    public void run() {
        try {
             executeReflectively();
       } catch(...){
          /// usual stuff
       }
    }
}

A concrete commend class:

@Command(...) 
final class CopyProfile extends BaseCommand {
    @Option String source;
    @Option String dest;

    // class must NOT be static and must be called "Executor"
    public class Executor implements CommandExecutor {
        @Override
        public Object doExecute() throws Exception {
           // you can basically wrap your original run() with this boilerplate
           // all the CopyProfile's field are in scope!
          FileUtils.copy(source, dest);
        }
    }
}

It seems like https://github.com/remkop/picocli/issues/500 may provide the ultimate solution to this. What are the other options until then?

Remko Popma
  • 35,130
  • 11
  • 92
  • 114
julius
  • 815
  • 2
  • 7
  • 12

1 Answers1

0

UPDATE February 2020:

Upgrading to a recent version of picocli should fix this issue. From the picocli 4.2.0 release notes:

From this release, subcommands are not instantiated until they are matched on the command line. This should improve the startup time for applications with subcommands that do a lot of initialization when they are instantiated.


An alternative that doesn’t require any code changes is to use GraalVM to compile your picocli-based application to a native image.

This article shows how to do this and the resulting startup time is 3 milliseconds.

Example picocli-based app running on GraalVM

Remko Popma
  • 35,130
  • 11
  • 92
  • 114
  • This is good, but be aware that if you import anything that uses `java.lang.SecurityManager`, this won't work. I learned this the hard way when I tried to do this with my application that interacted heavily with the AWS SDK. – SalmonKiller Dec 06 '18 at 22:28
  • @SalmonKiller This is good information, I wasn’t aware of this! Would you mind raising a ticket on the picocli issue tracker with some more details so we can document this for other users? – Remko Popma Dec 06 '18 at 23:03