1. Getting Started

To see what Spring Shell has to offer, we can write a trivial hello world shell application that has a simple argument.

Spring Shell is based on Spring Boot 3.0.1 and Spring Framework 6.0.3 and thus requires JDK 17.

1.1. Creating a Project

For the purpose of this tutorial, we create a simple Spring Boot application by using https://start.spring.io where you can choose Spring Shell dependency. This minimal application depends only on spring-boot-starter and spring-shell-starter.

Spring Shell version on start.spring.io is usually latest release.

With maven you’re expected to have something like:

<properties>
    <spring-shell.version>3.0.0-RC1</spring-shell.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-shell-starter</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-dependencies</artifactId>
            <version>${spring-shell.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

With gradle you’re expected to have something like:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.shell:spring-shell-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.shell:spring-shell-dependencies:3.0.0-RC1"
    }
}
Given that Spring Shell starts the REPL (Read-Eval-Print-Loop) because this dependency is present, you need to either skip tests when you build (-DskipTests) throughout this tutorial or remove the sample integration test that was generated by https://start.spring.io. If you do not remove it, the integration test creates the Spring ApplicationContext and, depending on your build tool, stays stuck in the eval loop or crashes with a NPE.

Once compiled it can be run either in interactive mode:

$ $JAVA_HOME/bin/java -jar demo-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.0.1)

2022-09-13T18:42:12.818+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: Starting DemoApplication using Java 17.0.4 on ...
2022-09-13T18:42:12.821+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: No active profile set, falling back to 1 default profile: "default"
2022-09-13T18:42:13.606+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: Started DemoApplication in 1.145 seconds (process running for 1.578)
shell:>help
AVAILABLE COMMANDS

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

Or in non-interactive mode:

$JAVA_HOME/bin/java -jar demo-0.0.1-SNAPSHOT.jar help

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.0.1)

2022-09-13T18:42:12.818+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: Starting DemoApplication using Java 17.0.4 on ...
2022-09-13T18:42:12.821+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: No active profile set, falling back to 1 default profile: "default"
2022-09-13T18:42:13.606+01:00  INFO 12644 --- [           main] com.example.demo.DemoApplication
: Started DemoApplication in 1.145 seconds (process running for 1.578)
AVAILABLE COMMANDS

Built-In Commands
       help: Display help about available commands
       stacktrace: Display the full stacktrace of the last error.
       clear: Clear the shell screen.
       quit, exit: Exit the shell.
       history: Display or save the history of previously run commands
       version: Show version info
       script: Read and execute commands from a file.
Check out [using-shell-customization-logging] making logging to work better with shell apps.

1.2. Your First Command

Now we can add our first command. To do so, create a new class (named whatever you want) and annotate it with @ShellComponent which is a variation of @Component that is used to restrict the set of classes that are scanned for candidate commands.

Then we can create a helloWorld method that takes String as an argument and returns it with "Hello world". Add @ShellMethod and optionally change command name using key parameter. You can use @ShellOption to define argument default value if it’s not given when running a command.

package com.example.demo;

import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class MyCommands {

	@ShellMethod(key = "hello-world")
	public String helloWorld(
		@ShellOption(defaultValue = "spring") String arg
	) {
		return "Hello world " + arg;
	}
}

New hello-world command becomes visible to help:

My Commands
       hello-world:

And you can run it:

shell:>hello-world
Hello world spring

shell:>hello-world --arg boot
Hello world boot

The rest of this document delves deeper into the whole Spring Shell programming model.