4. Commands

In this section, we go through an actual command registration and leave command options and execution for later in a documentation. You can find more detailed info in Command Registration.spring-doc.cn

4.1. Registration

There are two different ways to define a command: through an annotation model and through a programmatic model. In the annotation model, you define your methods in a class and annotate the class and the methods with specific annotations. In the programmatic model, you use a more low level approach, defining command registrations (either as beans or by dynamically registering with a command catalog).spring-doc.cn

4.1.1. Annotation Model

When you use the standard API, methods on beans are turned into executable commands, provided that:spring-doc.cn

  • The bean class bears the @ShellComponent annotation. (This is used to restrict the set of beans that are considered.)spring-doc.cn

  • The method bears the @ShellMethod annotation.spring-doc.cn

The @ShellComponent is a stereotype annotation that is itself meta-annotated with @Component. As a result, you can use it in addition to the filtering mechanism to declare beans (for example, by using @ComponentScan).spring-doc.cn

You can customize the name of the created bean by using the value attribute of the annotation.spring-doc.cn

@ShellComponent
static class MyCommands {

	@ShellMethod
	public void mycommand() {
	}
}

The only required attribute of the @ShellMethod annotation is its value attribute, which should have a short, one-sentence, description of what the command does. This lets your users get consistent help about your commands without having to leave the shell (see Help).spring-doc.cn

The description of your command should be short — no more than one or two sentences. For better consistency, it should start with a capital letter and end with a period.

By default, you need not specify the key for your command (that is, the word(s) that should be used to invoke it in the shell). The name of the method is used as the command key, turning camelCase names into dashed, gnu-style, names (for example, sayHello() becomes say-hello).spring-doc.cn

You can, however, explicitly set the command key, by using the key attribute of the annotation:spring-doc.cn

@ShellMethod(value = "Add numbers.", key = "sum")
public int add(int a, int b) {
	return a + b;
}
The key attribute accepts multiple values. If you set multiple keys for a single method, the command is registered with those different aliases.
The command key can contain pretty much any character, including spaces. When coming up with names though, keep in mind that consistency is often appreciated by users. That is, you should avoid mixing dashed-names with spaced names and other inconsistencies.

4.1.2. Programmatic Model

In the programmatic model, CommandRegistration can be defined as a @Bean and it will be automatically registered.spring-doc.cn

@Bean
CommandRegistration commandRegistration() {
	return CommandRegistration.builder()
		.command("mycommand")
		.build();
}

If all your commands have something in common, an instance of a CommandRegistration.BuilderSupplier is created which can be autowired. Default implementation of this supplier returns a new builder so you don’t need to worry about its internal state.spring-doc.cn

Commands registered programmatically automatically add help options mentioned in Help Options.

If bean of this supplier type is defined then auto-configuration will back off giving you an option to redefine default functionality.spring-doc.cn

@Bean
CommandRegistration commandRegistration(CommandRegistration.BuilderSupplier builder) {
	return builder.get()
		.command("mycommand")
		.build();
}

CommandRegistrationCustomizer beans can be defined if you want to centrally modify builder instance given you by supplier mentioned above.spring-doc.cn

@Bean
CommandRegistrationCustomizer commandRegistrationCustomizerExample() {
	return builder -> {
		// customize instance of CommandRegistration.Builder
	};
}

4.2. Organizing Commands

When your shell starts to provide a lot of functionality, you may end up with a lot of commands, which could be confusing for your users. By typing help, they would see a daunting list of commands, organized in alphabetical order, which may not always be the best way to show the available commands.spring-doc.cn

To alleviate this possible confusion, Spring Shell provides the ability to group commands together, with reasonable defaults. Related commands would then end up in the same group (for example, User Management Commands) and be displayed together in the help screen and other places.spring-doc.cn

By default, commands are grouped according to the class they are implemented in, turning the camelCase class name into separate words (so URLRelatedCommands becomes URL Related Commands). This is a sensible default, as related commands are often already in the class anyway, because they need to use the same collaborating objects.spring-doc.cn

If, however, this behavior does not suit you, you can override the group for a command in the following ways, in order of priority:spring-doc.cn

  1. Specify a group() in the @ShellMethod annotation.spring-doc.cn

  2. Place a @ShellCommandGroup on the class in which the command is defined. This applies the group for all commands defined in that class (unless overridden, as explained earlier).spring-doc.cn

  3. Place a @ShellCommandGroup on the package (through package-info.java) in which the command is defined. This applies to all the commands defined in the package (unless overridden at the method or class level, as explained earlier).spring-doc.cn

The following listing shows an example:spring-doc.cn

public class UserCommands {
    @ShellMethod(value = "This command ends up in the 'User Commands' group")
    public void foo() {}

    @ShellMethod(value = "This command ends up in the 'Other Commands' group",
    	group = "Other Commands")
    public void bar() {}
}

...

@ShellCommandGroup("Other Commands")
public class SomeCommands {
	@ShellMethod(value = "This one is in 'Other Commands'")
	public void wizz() {}

	@ShellMethod(value = "And this one is 'Yet Another Group'",
		group = "Yet Another Group")
	public void last() {}
}

4.3. Dynamic Command Availability

Registered commands do not always make sense, due to the internal state of the application. For example, there may be a download command, but it only works once the user has used connect on a remote server. Now, if the user tries to use the download command, the shell should explain that the command exists but that it is not available at the time. Spring Shell lets you do that, even letting you provide a short explanation of the reason for the command not being available.spring-doc.cn

There are three possible ways for a command to indicate availability. They all use a no-arg method that returns an instance of Availability. Consider the following example:spring-doc.cn

@ShellComponent
public class MyCommands {

    private boolean connected;

    @ShellMethod("Connect to the server.")
    public void connect(String user, String password) {
        [...]
        connected = true;
    }

    @ShellMethod("Download the nuclear codes.")
    public void download() {
        [...]
    }

    public Availability downloadAvailability() {
        return connected
            ? Availability.available()
            : Availability.unavailable("you are not connected");
    }
}

The connect method is used to connect to the server (details omitted), altering the state of the command through the connected boolean when done. The download command as marked as unavailable until the user has connected, thanks to the presence of a method named exactly as the download command method with the Availability suffix in its name. The method returns an instance of Availability, constructed with one of the two factory methods. If the command is not available, an explanation has to be provided. Now, if the user tries to invoke the command while not being connected, here is what happens:spring-doc.cn

shell:>download
Command 'download' exists but is not currently available because you are not connected.
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.

Information about currently unavailable commands is also used in the integrated help. See Help.spring-doc.cn

The reason provided when the command is not available should read nicely if appended after “Because”.spring-doc.cn

You should not start the sentence with a capital or add a final periodspring-doc.cn

If naming the availability method after the name of the command method does not suit you, you can provide an explicit name by using the @ShellMethodAvailability annotation:spring-doc.cn

    @ShellMethod("Download the nuclear codes.")
    @ShellMethodAvailability("availabilityCheck") (1)
    public void download() {
        [...]
    }

    public Availability availabilityCheck() { (1)
        return connected
            ? Availability.available()
            : Availability.unavailable("you are not connected");
    }
1 the names have to match

Finally, it is often the case that several commands in the same class share the same internal state and, thus, should all be available or unavailable as a group. Instead of having to stick the @ShellMethodAvailability on all command methods, Spring Shell lets you flip things around and put the @ShellMethodAvailabilty annotation on the availability method, specifying the names of the commands that it controls:spring-doc.cn

    @ShellMethod("Download the nuclear codes.")
    public void download() {
        [...]
    }

    @ShellMethod("Disconnect from the server.")
    public void disconnect() {
        [...]
    }

    @ShellMethodAvailability({"download", "disconnect"})
    public Availability availabilityCheck() {
        return connected
            ? Availability.available()
            : Availability.unavailable("you are not connected");
    }

The default value for the @ShellMethodAvailability.value() attribute is *. This special wildcard matches all command names. This makes it easy to turn all commands of a single class on or off with a single availability method:spring-doc.cn

@ShellComponent
public class Toggles {
  @ShellMethodAvailability
  public Availability availabilityOnWeekdays() {
    return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
      ? Availability.available()
      : Availability.unavailable("today is not Sunday");
  }

  @ShellMethod
  public void foo() {}

  @ShellMethod
  public void bar() {}
}
Spring Shell does not impose many constraints on how to write commands and how to organize classes. However, it is often good practice to put related commands in the same class, and the availability indicators can benefit from that.

4.4. Exception Handling

Exceptions happen from a user code wether it is intentional or not. This section describes how spring-shell handles exceptions and gives instructions and best practices how to work with it.spring-doc.cn

Many command line applications when applicable return an exit code which running environment can use to differentiate if command has been executed successfully or not. In a spring-shell this mostly relates when a command is run on a non-interactive mode meaning one command is always executed once with an instance of a spring-shell. Take a note that exit code always relates to non-interactive shell.spring-doc.cn

4.4.1. Exception Resolving

Unhandled exceptions will bubble up into shell’s ResultHandlerService and then eventually handled by some instance of ResultHandler. Chain of ExceptionResolver implementations can be used to resolve exceptions and gives you flexibility to return message to get written into console together with exit code which are wrapped within CommandHandlingResult. CommandHandlingResult may contain a message and/or exit code.spring-doc.cn

static class CustomExceptionResolver implements CommandExceptionResolver {

	@Override
	public CommandHandlingResult resolve(Exception e) {
		if (e instanceof CustomException) {
			return CommandHandlingResult.of("Hi, handled exception\n", 42);
		}
		return null;
	}
}

CommandExceptionResolver implementations can be defined globally as bean.spring-doc.cn

@Bean
CustomExceptionResolver customExceptionResolver() {
	return new CustomExceptionResolver();
}

or defined per CommandRegistration if it’s applicable only for a particular command itself.spring-doc.cn

CommandRegistration.builder()
	.withErrorHandling()
		.resolver(new CustomExceptionResolver())
		.and()
	.build();
Resolvers defined with a command are handled before global resolvers.

Use you own exception types which can also be an instance of boot’s ExitCodeGenerator if you want to define exit code there.spring-doc.cn

static class CustomException extends RuntimeException implements ExitCodeGenerator {

	@Override
	public int getExitCode() {
		return 0;
	}
}

Some build in CommandExceptionResolver beans are registered to handle common exceptions thrown from command parsing. These are registered with order presedence defined in CommandExceptionResolver.DEFAULT_PRECEDENCE. As these beans are used in a given order, @Order annotation or Ordered interface from can be used just like in any other spring app. This is generally useful if you need to control your own beans to get used either before or after a defaults.spring-doc.cn

4.4.2. Exit Code Mappings

Default behaviour of an exit codes is as:spring-doc.cn

Every CommandRegistration can define its own mappings between Exception and exit code. Essentially we’re bound to functionality in Spring Boot regarding exit code and simply integrate into that.spring-doc.cn

Assuming there is an exception show below which would be thrown from a command:spring-doc.cn

static class MyException extends RuntimeException {

	private final int code;

	MyException(String msg, int code) {
		super(msg);
		this.code = code;
	}

	public int getCode() {
		return code;
	}
}

It is possible to define a mapping function between Throwable and exit code. You can also just configure a class to exit code which is just a syntactic sugar within configurations.spring-doc.cn

CommandRegistration.builder()
	.withExitCode()
		.map(MyException.class, 3)
		.map(t -> {
			if (t instanceof MyException) {
				return ((MyException) t).getCode();
			}
			return 0;
		})
		.and()
	.build();
Exit codes cannot be customized with annotation based configuration

4.4.3. @ExceptionResolver

@ShellComponent classes can have @ExceptionResolver methods to handle exceptions from component methods. These are meant for annotated methods.spring-doc.cn

The exception may match against a top-level exception being propagated (e.g. a direct IOException being thrown) or against a nested cause within a wrapper exception (e.g. an IOException wrapped inside an IllegalStateException). This can match at arbitrary cause levels.spring-doc.cn

For matching exception types, preferably declare the target exception as a method argument, as the preceding example(s) shows. When multiple exception methods match, a root exception match is generally preferred to a cause exception match. More specifically, the ExceptionDepthComparator is used to sort exceptions based on their depth from the thrown exception type.spring-doc.cn

Alternatively, the annotation declaration may narrow the exception types to match, as the following example shows:spring-doc.cn

@ExceptionResolver({ RuntimeException.class })
CommandHandlingResult errorHandler(Exception e) {
	// Exception would be type of RuntimeException,
	// optionally do something with it
	return CommandHandlingResult.of("Hi, handled exception\n", 42);
}
@ExceptionResolver
CommandHandlingResult errorHandler(RuntimeException e) {
	return CommandHandlingResult.of("Hi, handled custom exception\n", 42);
}

@ExceptionResolver can also return String which is used as an output to console. You can use @ExitCode annotation to define return code.spring-doc.cn

@ExceptionResolver
@ExitCode(code = 5)
String errorHandler(Exception e) {
	return "Hi, handled exception";
}

@ExceptionResolver with void return type is automatically handled as handled exception. You can then also define @ExitCode and use Terminal if you need to write something into console.spring-doc.cn

@ExceptionResolver
@ExitCode(code = 5)
void errorHandler(Exception e, Terminal terminal) {
	PrintWriter writer = terminal.writer();
	String msg =  "Hi, handled exception " + e.toString();
	writer.println(msg);
	writer.flush();
}
Method Arguments

@ExceptionResolver methods support the following arguments:spring-doc.cn

Method argument Description

Exception typespring-doc.cn

For access to the raised exception. This is any type of Exception or Throwable.spring-doc.cn

Terminalspring-doc.cn

For access to underlying JLine terminal to i.e. get its terminal writer.spring-doc.cn

Return Values

@ExceptionResolver methods support the following return values:spring-doc.cn

Return value Description

Stringspring-doc.cn

Plain text to return to a shell. Exit code 1 is used in this case.spring-doc.cn

CommandHandlingResultspring-doc.cn

Plain CommandHandlingResult having message and exit code.spring-doc.cn

voidspring-doc.cn

A method with a void return type is considered to have fully handled the exception. Usually you would define Terminal as a method argument and write response using terminal writer from it. As exception is fully handled, Exit code 0 is used in this case.spring-doc.cn

4.5. Hidden Command

It is possible to hide a command which is convenient in cases where it is not yet ready for prime time, is meant for debugging purposes or you have any other reason you dont want to advertise its presense.spring-doc.cn

Hidden command can be executed if you know it and its options. It is effectively removed from:spring-doc.cn

Below is an example how to define command as hidden. It shows available builder methods to define hidden state.spring-doc.cn

CommandRegistration commandRegistration() {
	return CommandRegistration.builder()
		.command("mycommand")
		// define as hidden
		.hidden()
		// can be defined via a flag (false)
		.hidden(false)
		// can be defined via a flag (true)
		.hidden(true)
		.build();
}
Defining hidden commands is not supported with annotation based configuration

4.6. Help Options

Spring Shell has a build-in help command but not all favour getting command help from it as you always need to call it with arguments for target command. It’s common in many cli frameworks for every command having options --help and -h to print out command help.spring-doc.cn

Default functionality is that every command will get modified to have options --help and -h, which if present in a given command will automatically short circuit command execution into a existing help command regardless what other command-line options is typed.spring-doc.cn

Below example shows its default settings.spring-doc.cn

@Bean
CommandRegistration commandRegistration() {
	return CommandRegistration.builder()
		.command("mycommand")
		.withHelpOptions()
			.enabled(true)
			.longNames("help")
			.shortNames('h')
			.command("help")
			.and()
		.build();
}

It is possible to change default behaviour via configuration options.spring-doc.cn

spring:
  shell:
    help:
      enabled: true
      long-names: help
      short-names: h
      command: help
Commands defined programmationally or via annotations will automatically add help options. With annotation model you can only turn things off globally, programmatic model gives option to modify settings per command.

4.7. Interaction Mode

Command registration can define InteractionMode which is used to hide commands depending which mode shell is executing. More about that in Interaction Mode.spring-doc.cn

You can define it with CommandRegisration.spring-doc.cn

CommandRegistration commandRegistration() {
	return CommandRegistration.builder()
		.command("mycommand")
		// can be defined for all modes
		.interactionMode(InteractionMode.ALL)
		// can be defined only for interactive
		.interactionMode(InteractionMode.INTERACTIVE)
		// can be defined only for non-interactive
		.interactionMode(InteractionMode.NONINTERACTIVE)
		.build();
}

Or with @ShellMethod.spring-doc.cn

@ShellMethod(key = "mycommand", interactionMode = InteractionMode.INTERACTIVE)
public void mycommand() {
}

4.8. Built-In Commands

4.8.1. Help

Running a shell application often implies that the user is in a graphically limited environment. Also, while we are nearly always connected in the era of mobile phones, accessing a web browser or any other rich UI application (such as a PDF viewer) may not always be possible. This is why it is important that the shell commands are correctly self-documented, and this is where the help command comes in.spring-doc.cn

Typing help + ENTER lists all the commands known to the shell (including unavailable commands) and a short description of what they do, similar to the following:spring-doc.cn

my-shell:>help
AVAILABLE COMMANDS

Built-In Commands
       exit: Exit the shell.
       help: Display help about available commands
       stacktrace: Display the full stacktrace of the last error.
       clear: Clear the shell screen.
       quit: Exit the shell.
       history: Display or save the history of previously run commands
       completion bash: Generate bash completion script
       version: Show version info
       script: Read and execute commands from a file.

Typing help <command> shows more detailed information about a command, including the available parameters, their type, whether they are mandatory or not, and other details.spring-doc.cn

The following listing shows the help command applied to itself:spring-doc.cn

my-shell:>help help
NAME
       help - Display help about available commands

SYNOPSIS
       help --command String

OPTIONS
       --command or -C String
       The command to obtain help for.
       [Optional]

Help is templated and can be customized if needed. Settings are under spring.shell.command.help where you can use enabled to disable command, grouping-mode taking group or flat if you want to hide groups by flattening a structure, command-template to define your template for output of a command help, commands-template to define output of a command list.spring-doc.cn

If spring.shell.command.help.grouping-mode=flat is set, then help would show:spring-doc.cn

my-shell:>help help
AVAILABLE COMMANDS

exit: Exit the shell.
help: Display help about available commands
stacktrace: Display the full stacktrace of the last error.
clear: Clear the shell screen.
quit: Exit the shell.
history: Display or save the history of previously run commands
completion bash: Generate bash completion script
version: Show version info
script: Read and execute commands from a file.

Output from help and help <commmand> are both templated with a default implementation which can be changed.spring-doc.cn

Option spring.shell.command.help.commands-template defaults to classpath:template/help-commands-default.stg and is passed GroupsInfoModel as a model.spring-doc.cn

Option spring.shell.command.help.command-template defaults to classpath:template/help-command-default.stg and is passed CommandInfoModel as a model.spring-doc.cn

Table 1. GroupsInfoModel Variables
Key Description

showGroupsspring-doc.cn

true if showing groups is enabled. Otherwise, false.spring-doc.cn

groupsspring-doc.cn

The commands variables (see GroupCommandInfoModel Variables).spring-doc.cn

commandsspring-doc.cn

The commands variables (see CommandInfoModel Variables).spring-doc.cn

hasUnavailableCommandsspring-doc.cn

true if there is unavailable commands. Otherwise, false.spring-doc.cn

Table 2. GroupCommandInfoModel Variables
Key Description

groupspring-doc.cn

The name of a group, if set. Otherwise, empty.spring-doc.cn

commandsspring-doc.cn

The commands, if set. Otherwise, empty. Type is a multi value, see CommandInfoModel Variables.spring-doc.cn

Table 3. CommandInfoModel Variables
Key Description

namespring-doc.cn

The name of a command, if set. Otherwise, null. Type is string and contains full command.spring-doc.cn

namesspring-doc.cn

The names of a command, if set. Otherwise, null. Type is multi value essentially name splitted.spring-doc.cn

aliasesspring-doc.cn

The possible aliases, if set. Type is multi value with strings.spring-doc.cn

descriptionspring-doc.cn

The description of a command, if set. Otherwise, null.spring-doc.cn

parametersspring-doc.cn

The parameters variables, if set. Otherwise empty. Type is a multi value, see CommandParameterInfoModel Variables.spring-doc.cn

availabilityspring-doc.cn

The availability variables (see CommandAvailabilityInfoModel Variables).spring-doc.cn

Table 4. CommandParameterInfoModel Variables
Key Description

typespring-doc.cn

The type of a parameter if set. Otherwise, null.spring-doc.cn

argumentsspring-doc.cn

The arguments, if set. Otherwise, null. Type is multi value with strings.spring-doc.cn

requiredspring-doc.cn

true if required. Otherwise, false.spring-doc.cn

descriptionspring-doc.cn

The description of a parameter, if set. Otherwise, null.spring-doc.cn

defaultValuespring-doc.cn

The default value of a parameter, if set. Otherwise, null.spring-doc.cn

hasDefaultValuespring-doc.cn

true if defaultValue exists. Otherwise, false.spring-doc.cn

Table 5. CommandAvailabilityInfoModel Variables
Key Description

availablespring-doc.cn

true if available. Otherwise, false.spring-doc.cn

reasonspring-doc.cn

The reason if not available if set. Otherwise, null.spring-doc.cn

4.8.2. Clear

The clear command does what you would expect and clears the screen, resetting the prompt in the top left corner.spring-doc.cn

4.8.3. Exit

The quit command (also aliased as exit) requests the shell to quit, gracefully closing the Spring application context. If not overridden, a JLine History bean writes a history of all commands to disk, so that they are available again on the next launch.spring-doc.cn

4.8.4. Stacktrace

When an exception occurs inside command code, it is caught by the shell and a simple, one-line message is displayed so as not to overflow the user with too much information. There are cases, though, when understanding what exactly happened is important (especially if the exception has a nested cause).spring-doc.cn

To this end, Spring Shell remembers the last exception that occurred, and the user can later use the stacktrace command to print all the details on the console.spring-doc.cn

4.8.5. Script

The script command accepts a local file as an argument and replays commands found there, one at a time.spring-doc.cn

Reading from the file behaves exactly like inside the interactive shell, so lines starting with // are considered to be comments and are ignored, while lines ending with \ trigger line continuation.spring-doc.cn

4.8.6. History

The history command shows the history of commands that has been executed.spring-doc.cn

There are a few configuration options that you can use to configure behavior of a history. History is kept in a log file, which is enabled by default and can be turned off by setting spring.shell.history.enabled. The name of a log file is resolved from spring.application.name and defaults to spring-shell.log, which you can change by setting spring.shell.history.name.spring-doc.cn

By default, a log file is generated to a current working directory, which you can dictate by setting spring.shell.config.location. This property can contain a placeholder ({userconfig}), which resolves to a common shared config directory.spring-doc.cn

Run the Spring Shell application to see how the sample application works as it uses these options.

4.8.7. Completion

The completion command set lets you create script files that can be used with am OS shell implementations to provide completion. This is very useful when working with non-interactive mode.spring-doc.cn

Currently, the only implementation is for bash, which works with bash sub-command.spring-doc.cn

4.8.8. Version

The version command shows existing build and git info by integrating into Boot’s BuildProperties and GitProperties if those exist in the shell application. By default, only version information is shown, and you can enable other information through configuration options.spring-doc.cn

The relevant settings are under spring.shell.command.version, where you can use enabled to disable a command and, optionally, define your own template with template. You can use the show-build-artifact, show-build-group, show-build-name, show-build-time, show-build-version, show-git-branch, show-git-commit-id, show-git-short-commit-id and show-git-commit-time commands to control fields in a default template.spring-doc.cn

The template defaults to classpath:template/version-default.st, and you can define your own, as the following example shows:spring-doc.cn

<buildVersion>

This setting would output something like the following:spring-doc.cn

X.X.X

You can add the following attributes to the default template rendering: buildVersion, buildGroup, buildGroup, buildName, buildTime, gitShortCommitId, gitCommitId, gitBranch, and gitCommitTime.spring-doc.cn

4.9. Writing

When something needs to get written into your console you can always use JDK’s System.out which then goes directly into JDK’s own streams. Other recommended way is to use JLine’s Terminal and get writer instance from there.spring-doc.cn

If using target endpoints, i.e. consumer which is not expected to return anything given CommandContext contains reference to Terminal and writer can be accessed from there.spring-doc.cn

CommandRegistration.builder()
	.command("example")
	.withTarget()
		.consumer(ctx -> {
			ctx.getTerminal().writer().println("hi");
			ctx.getTerminal().writer().flush();
		})
		.and()
	.build();

It’s possible to autowire Terminal to get access to its writer.spring-doc.cn

@Autowired
Terminal terminal;

@ShellMethod
public void example() {
	terminal.writer().println("hi");
	terminal.writer().flush();
}