Spring Shell: Building Interactive CLI Applications
While web applications and REST APIs dominate the Spring ecosystem, command-line tools remain essential for automation, administration, and developer tooling. Spring Shell brings the power and familiarity of the Spring programming model to interactive CLI applications, letting you build robust command-line tools with minimal boilerplate.
In this post, we'll explore how to build a fully functional interactive shell application using Spring Shell.
What Is Spring Shell?
Spring Shell is a framework for building command-line applications on top of Spring Boot. It provides:
- An interactive shell (REPL) with command history and tab completion
- Annotation-based command definitions
- Built-in commands (help, clear, exit, etc.)
- Input parsing and type conversion
- Dynamic command availability
- Customizable prompts and output
It's ideal for building admin tools, DevOps utilities, database clients, or any interactive terminal application.
Getting Started
Add the Spring Shell starter to your pom.xml:
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>3.2.0</version>
</dependency>
Or with Gradle:
implementation 'org.springframework.shell:spring-shell-starter:3.2.0'
Your main application class is a standard Spring Boot application:
@SpringBootApplication
public class CliApplication {
public static void main(String[] args) {
SpringApplication.run(CliApplication.class, args);
}
}
When you run this application, Spring Shell automatically launches an interactive shell.
Your First Command
Commands are defined using @ShellComponent and @ShellMethod:
@ShellComponent
public class GreetingCommands {
@ShellMethod(key = "hello", value = "Print a greeting message")
public String hello(
@ShellOption(defaultValue = "World") String name) {
return "Hello, " + name + "!";
}
}
Now run the application and try it:
shell:> hello
Hello, World!
shell:> hello --name Alice
Hello, Alice!
The key attribute defines the command name, and value provides the description shown in help output.
Working with Command Options
Spring Shell offers rich support for command arguments through @ShellOption.
Positional and Named Arguments
@ShellComponent
public class MathCommands {
@ShellMethod(key = "add", value = "Add two numbers")
public int add(int a, int b) {
return a + b;
}
@ShellMethod(key = "divide", value = "Divide two numbers")
public double divide(
@ShellOption(help = "The dividend") double dividend,
@ShellOption(help = "The divisor") double divisor) {
if (divisor == 0) {
throw new IllegalArgumentException("Cannot divide by zero");
}
return dividend / divisor;
}
}
Usage:
shell:> add 5 3
8
shell:> divide --dividend 10 --divisor 4
2.5
Boolean Flags
Boolean options act as flags — their presence sets them to true:
@ShellMethod(key = "list-files", value = "List files in a directory")
public String listFiles(
@ShellOption(defaultValue = ".") String path,
@ShellOption(value = "--verbose") boolean verbose) {
// ... implementation
return verbose ? "Detailed listing..." : "Simple listing...";
}
shell:> list-files --verbose
Detailed listing...
Arity for Multiple Values
Use arity to accept multiple values for a single option:
@ShellMethod(key = "sum", value = "Sum multiple numbers")
public int sum(
@ShellOption(arity = 5, defaultValue = "0") int[] numbers) {
return Arrays.stream(numbers).sum();
}
shell:> sum --numbers 1 2 3 4 5
15
Dynamic Command Availability
A powerful feature of Spring Shell is the ability to enable or disable commands based on application state. For example, certain commands should only be available after a user logs in.
@ShellComponent
public class SecureCommands {
private boolean connected = false;
@ShellMethod(key = "connect", value = "Connect to the server")
public String connect(String server) {
this.connected = true;
return "Connected to " + server;
}
@ShellMethod(key = "disconnect", value = "Disconnect from the server")
public String disconnect() {
this.connected = false;
return "Disconnected";
}
@ShellMethod(key = "status", value = "Show connection status")
public String status() {
return "Fetching status from server...";
}
public Availability statusAvailability() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
The statusAvailability() method follows a naming convention: <commandMethod>Availability. When status is unavailable, Spring Shell provides a helpful message:
shell:> status
Command 'status' exists but is not currently available because you are not connected
Grouping and Organizing Commands
By default, commands are grouped by their containing class. You can customize groups explicitly:
java
@ShellComponent
@ShellCommandGroup("User Management")
public class UserCommands {
@ShellMethod(key = "user-create", value = "Create a new user")
public String createUser(String username) {
return "Created user: " + username;
}
@Shell
Top comments (1)
The dynamic command availability feature you mentioned is a standout aspect of Spring Shell, allowing for a highly interactive user experience. It opens up avenues for more context-aware command execution, which can really enhance usability in complex applications. One improvement idea could be to implement a command that lists available commands based on user permissions or roles, making it even more user-friendly. If you're looking for help in expanding this feature or any other part of your project, I’d be glad to explore a paid collaboration. What other enhancements are you considering for user experience?