I could swear I had remembered Spring Boot already supporting something like this, but I can't find it. So..

I haven't actually used it yet, but picocli/ looks awesome. here's the problem I see with current solution

  • Manually generate help
  • not typesafe out of the box
  • convoluted code has to be written for conditional things
  • no subcommands
  • no colored help output
  • no command completion
  • ...

picocli seems to solve these, suggested spring boot interface

package com.xenoterracide.ppm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import picocli.CommandLine;

import java.util.function.Consumer;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run( Application.class, args );
  }

  @Component
  static class Printer implements Consumer<String> {

    @Override
    public void accept( String s ) {
      System.out.println("Hello, " + s);
    }
  }

  @CommandLine.Command // auto register as component
  static class RootCommand implements Runnable {

    private final Printer printer;

    @CommandLine.Parameters(index = "0" )
    private String to;

    RootCommand( Printer printer ) {
      this.printer = printer;
    }

    @Override
    public void run() {
      printer.accept( to );
    }
  }
}

would be equivalent to

package com.xenoterracide.ppm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import picocli.CommandLine;

import java.util.function.Consumer;

// CHECKSTYLE:OFF HideUtilityClassConstructor FinalClass
@SpringBootApplication
public class Application {
  // CHECKSTYLE:ON

  public static void main(String[] args) {
    var ctx = SpringApplication.run( Application.class, args );
    var rootCommand = ctx.getBean( RootCommand.class );
    new CommandLine( rootCommand ).execute( args );
  }

  @Component
  static class Printer implements Consumer<String> {

    @Override
    public void accept( String s ) {
      System.out.println("Hello, " + s);
    }
  }

  @Component
  @CommandLine.Command
  static class RootCommand implements Runnable {

    private final Printer printer;

    @CommandLine.Parameters(index = "0" )
    private String to;

    RootCommand( Printer printer ) {
      this.printer = printer;
    }

    @Override
    public void run() {
      printer.accept( to );
    }
  }
}

obviously in this example it's fairly trivial to workaround the command not being a bean by default, but if you get into subcommands declared declaratively, if they aren't all wired up they aren't going to work. Possible, even probable you'd have to have a @Commands spring annotation that could replace this with a similar api, but would ensure autowiring of all those classes, all the way down.

@Command(subcommands = {
    GitStatus.class,
    GitCommit.class,
    GitAdd.class,
    GitBranch.class,
    GitCheckout.class,
    GitClone.class,
    GitDiff.class,
    GitMerge.class,
    GitPush.class,
    GitRebase.class,
    GitTag.class
})
public class Git { /* ... */ }

honestly, I think that you should probably use this thing for other spring commands, just 'cause pretty, and dry. Just an idea, to be honest I don't need this at the moment due to not writing cli apps with spring boot very often. But if I were writing a java cli app, this is how I'd want to do it.

Comment From: snicoll

Thanks for the suggestion but we do not plan to make any enhancements to Spring Boot’s CLI at this time.

Comment From: xenoterracide

just to be clear, I totally mean the primary use case of this to be for a consumer API's. I suppose though if the time comes when I need it I can just create my own spring module, I was just hoping it would be there instead, because when I did use the existing API and needed some conditional option it got painful real fast.