3. Developing Spring Shell Applications

Contributing commands to the shell is very easy. There are only a few annotations you need to learn. The implementation style of the command is the same as developing classes in for application that uses dependency injection. You can leverage all the features of the Spring container to implement your command classes.

3.1 Marker Interface

The first step to creating a command is to implement the marker interface CommandMarker and to annotate your class with Spring's @Component annotation. (Note there is an open JIRA issue to provide a @CliCommand meta-annotation to avoid having to use a marker interface). Using the code from the helloworld sample application, the shell of a HelloWorldCommands class is shown below

public class HelloWorldCommands implements CommandMarker {
  // use any Spring annotations for Dependency Injection or other Spring interfaces as required.

  // methods with @Cli annotations go here  


3.2 Logging

Logging is currently done using JDK logging. Due to the intrincacies of console, JLine and Ansi handling, it is generally advised to display messages as return values to the method commands. However, when logging is required, the typical JDK logger declaration should suffice.

public class HelloWorldCommands implements CommandMarker {
  protected final Logger LOG = Logger.getLogger(getClass().getName());

  // methods with @Cli annotations go here  

Note it's the packager/developer responsability to handle logging for third-party libraries. Typically one wants to reduce the logging level so the console/shell does not get affected by logging messages.

3.3 CLI Annotations

There are three annotations used on methods and method arguments that define main contract for interacting with the shell. These are

  • CliAvailabilityIndicator - Placed on a method that returns a boolean value and indicates if a particular command can be presented in the shell. This decision is usually based on the history of commands that have been executed previously. It prevents extraneous commands being presented until some preconditions are met, for example the execution of a 'configuration' command.

  • CliCommand - Placed on a method that provides a command to the shell. Its value provides one or more strings that serve as the start of a particular command name. These must be unique within the entire application, across all plugins.

  • CliOptions - Placed on the arguments of a command methods, allowing it to declare the argument value as mandatory or optional with a default value.

Here is a simple use of these annotations in a command class

public class HelloWorldCommands implements CommandMarker {

  @CliAvailabilityIndicator({"hw simple"})
  public boolean isCommandAvailable() {
    return true;

  @CliCommand(value = "hw simple", help = "Print a simple hello world message")
  public String simple(
    @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message,
    @CliOption(key = { "location" }, mandatory = false, help = "Where you are saying hello", specifiedDefaultValue="At work") 
                 final String location) {

    return "Message = [" + message + "] Location = [" + location + "]";


The method annotated with @CliAvailabilityIndicator is returning true so that the one and only command in this class is exposed to the shell to be invoked. If there were more commands in the class, you would list them as comma separated value.

The @CliCommand annotation is creating the command 'hw simple' in the shell. The help message is what will be printed if you use the build in help command. The method name is 'simple' but it could just have well been any other name.

The @CliOption annotation on each of the command arguments is where you will spend most of your time authoring commands. You need to decide which arguments are required, which are optional, and if they are optional is there a default value. In this case there are two arguments or keys to the command, message and location. The key message is required and a help message is provided to give guidance to the user when tabbing to get completion for the command.

The implementation of the 'simple' method is trivial, just a log statement, but this is where you would typically call other collaborating objects that were injected into the class via Spring.

The method argument types in this example are String, which doesn't present any issue with type conversion. You can specify methods with any rich object type as well as basic primitive types such as int, float etc. For all types other than those handled by the shell by default (basic types, Date, File) you will need to register your own implementation of the org.springframework.shell.core.Converter interface with the container in your plugin.

Note that the the method return argument can be non-void - in our example, it is the actual message we want to display. Whenever an object is returned, the shell will display its toString() representation.

3.4 Building and running the shell

In our opinion, the easiest way to build an execute the shell is to cut-n-paste the gradle script in the example application. This uses the application plugin from gradle to create a bin directory with a startup script for windows and Unix and places all dependent jars in a lib directory. Maven has a similar plugin - the AppAssembler plugin.

The main class of the shell is org.springframework.shell.Bootstrap. As long as you place other plugins, perhaps developed independently, on the classpath, the Bootstrap class will incorporate them into the shell.