-
Notifications
You must be signed in to change notification settings - Fork 395
v4 migration guide
This guide aims at helping you migrate your Spring Shell applications from v3 to v4. It covers the main breaking changes and deprecations introduced in Spring Shell v4.
If a change is missing, please report it by opening an issue on GitHub.
Spring Shell 4 is based on Spring Framework 7. Spring Boot support in Spring Shell now requires Spring Boot 4+. Please make sure to upgrade your dependencies accordingly.
- The
spring-shell-coremodule does NOT depend on Spring Boot anymore. If you are using Spring Shell in a Spring Boot application, please add an explicit dependency tospring-shell-core-autoconfigureor one of the Spring Shell starters. - The
spring-shell-coremodule does NOT depend on JLine anymore. By default, the core module uses the system'sjava.io.Consoledefined in the JDK. If you want to use the JLine console, please add an explicit dependency tospring-shell-jline. - The modules
spring-shell-standardandspring-shell-standard-commandshave been merged in thespring-shell-coremodule. You do not need to add any explicit dependency to these modules anymore. - The module
spring-shell-tablehas been merged inspring-shell-jline. You do not need to add any explicit dependency to this module anymore if you are using JLine. - The module
spring-shell-autoconfigurehas been renamed tospring-shell-core-autoconfigure.
The legacy annotations @ShellComponent, @ShellMethod, @ShellOption and related annotations that were deprecated in v3 have been removed in v4. Please use the new annotations @Command, @Option, @Argument and other annotations defined in org.springframework.shell.core.command.annotation of the spring-shell-core module.
It is not possible to use the @Command annotation on a top-level class anymore. The following confusing (!) usage is no longer supported:
@Command
class Example {
@Command(command = "example")
public String example() {
return "Hello";
}
}Moreover, the attribute command of the @Command annotation has been renamed to name for better clarity.
However, annotated methods should still be defined in a Spring-managed bean (e.g., @Component, @Service, etc.).
In Spring Shell v4, it is recommended to define (related) commands in dedicated classes. For example:
@Component
public class GitHubCommands {
@Command(name = { "github", "auth", "login" }, description = "Login to GitHub", group = "github")
public void githubLogin(CommandContext commandContext) {
PrintWriter writer = commandContext.outputWriter();
writer.println("Logging in to GitHub...");
writer.flush();
}
@Command(name = { "github", "auth", "logout" }, description = "Logout from GitHub", group = "github")
public void githubLogout(CommandContext commandContext) {
PrintWriter writer = commandContext.outputWriter();
writer.println("Logging out from GitHub...");
writer.flush();
}
}In Spring Shell 3, it was required to use @EnableCommand or @CommandScan even when using Spring Boot. This is NOT the opinionated Boot way of discovering/doing things, and is not consistent with the rest of the portfolio.
In Spring Shell 4, if you are using Spring Boot, command scanning is automatically enabled (and therefore, @CommandScan was removed). You do not need to add any explicit annotation to your configuration anymore. For example:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.Option;
@SpringBootApplication
public class SpringShellApplication {
public static void main(String[] args) {
SpringApplication.run(SpringShellApplication.class, args);
}
@Command
public void hi() {
System.out.println("Hello world!");
}
}However, if you are not using Spring Boot, you still need to use @EnableCommand to enable command discovery. For example:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.shell.core.ShellRunner;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.EnableCommand;
import org.springframework.shell.core.command.annotation.Option;
@EnableCommand(SpringShellApplication.class)
public class SpringShellApplication {
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringShellApplication.class);
ShellRunner runner = context.getBean(ShellRunner.class);
runner.run(args);
}
@Command
public void hi() {
System.out.println("Hello world!");
}
}The main issues related to this change are https://github.com/spring-projects/spring-shell/issues/1158 and https://github.com/spring-projects/spring-shell/issues/1206.
In Spring Shell 3, customizing commands required a number of different annotations, which was confusing to many users. In Spring Shell 4, all command-related customizations are done via the @Command annotation and its attributes.
For example, customizing command availability can be done via a single annotation instead of two annotations:
// v3
@Command(name = "download")
@CommandAvailability("loginAvailabilityProvider")
public void downloadCommand() {
// command implementation
}
// v4
@Command(name = "download", availabilityProvider = "loginAvailabilityProvider")
public void downloadCommand() {
// command implementation
}The same applies to other customizations such as exception mapping to exit codes, command completion, etc.
In Spring Shell 4, command options can have only a single value of short/long options names. Please refer to https://github.com/spring-projects/spring-shell/issues/1220 for more details about this change and reason behind it.
Moreover, option labels were removed as they are not used to order/filter/group options and only used for the help string. In Spring Shell 4, options can only have a single short/long name.
In Spring Shell 3, the fact that option completion was separate for each option had (apart from having to define several completer beans for a single command) a big limitation as it was impossible to provide cross-options completion.
In Spring Shell 4, command completion is centralized and customizable at the command-level by providing a custom CompletionProvider. Here is an example:
enum Gender {
MALE, FEMALE
}
@Command(name = "hello", description = "Say hello to a given name", group = "Greetings",
help = "A command that greets the user with 'Hello ${name}!'. Usage: hello [-g | --gender]=<gender> [-n | --name]=<name>",
completionProvider = "helloCompletionProvider")
public void sayHello(
@Option(shortName = 'n', longName = "name", description = "the name of the person to greet", defaultValue = "World") String name,
@Option(shortName = 'g', longName = "gender", description = "the gender of the person to greet") Gender gender) {
String prefix = switch (gender) {
case MALE -> "Mr. ";
case FEMALE -> "Ms. ";
};
System.out.println("Hello " + prefix + name + "!");
}
@Bean
public CompletionProvider helloCompletionProvider() {
// if gender is female, suggest "Alice" and "Eve", otherwise suggest "Bob" and "Charlie"
return completionContext ->
{
CommandOption commandOption = completionContext.getCommandOption();
// check option name and value and return appropriate proposals
return List.of();
};
}The @OptionValues annotation is not used anymore and therefore has been removed.
In Spring Shell 4, the APIs for programmatic command definition and registration have been revamped to align with the new declarative command definition approach.
The main entry point for programmatic command registration is the CommandRegistry interface. You can create commands using the Command.Builder class and register them with the CommandRegistry. For example:
@Bean
public Command myCommand() {
return Command.builder().name("mycommand").execute(context -> {
context.outputWriter().println("This is my command!");
});
}With Spring Shell 4, programmatic command registration is simplified and more consistent with the declarative approach and any bean of type Command will be automatically registered as a command.
Please refer to the official documentation for more details and examples.
In Spring Shell 4, it is not possible to use several ShellRunner implementations in the same application anymore. This was confusing both in terms of configuration and usage. You can obviously still define multiple shell implementations in the same context if you want, however, you will need to choose which one to use at startup time.
Spring Shell 4 provides 3 implementations of the ShellRunner interface out of the box:
-
SystemShellRunner: A interactive shell implementation based on the standard Java Console (requires no additional dependency). This is a new addition in Spring Shell v4. -
JLineShellRunner: A interactive shell implementation based on JLine (requiresspring-shell-jlinedependency). -
NonInteractiveShellRunner: A non-interactive shell implementation that is designed for scripting and automation scenarios.
By default, Spring Shell will use the SystemShellRunner implementation when using the @EnableCommand annotation:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.shell.core.ShellRunner;
import org.springframework.shell.core.command.annotation.Command;
import org.springframework.shell.core.command.annotation.EnableCommand;
@EnableCommand(SpringShellApplication.class)
public class SpringShellApplication {
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringShellApplication.class);
ShellRunner runner = context.getBean(ShellRunner.class);
runner.run(args);
}
@Command
public void hi() {
System.out.println("Hello world!");
}
}The SystemShellRunner provides basic shell functionalities using the standard Java Console. However, it does not support advanced features such as command history, tab completion, and rich text formatting. If you want to use the JLine-based shell which is more feature-rich, you need to explicitly define a JLineShellRunner bean in your configuration or use the Spring Boot starter that does this for you:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.shell.core.command.annotation.Command;
@SpringBootApplication
public class SpringShellApplication {
public static void main(String[] args) {
// This will start the JLine-based interactive shell
SpringApplication.run(SpringShellApplication.class, args);
}
@Command
public void hi() {
System.out.println("Hello world!");
}
}Following the Unix philosophy of doing one thing at a time and dong it well, it does not make sense for a shell application to be interactive and non-interactive at the same time. In Spring Shell 4, you have to choose which mode to be in from the beginning.
By default, Spring Shell 4 applications are interactive. If you want to disable interactivity, you can do so by setting the spring.shell.interactive.enabled property to false.
In non-interactive mode, the application will execute the command provided as argument and then exit.
Related to the previous point, commands can no longer be marked as interactive or non-interactive. All commands are now available in both modes and are executed in the mode the shell was started in.
The InteractionMode enumeration is not used anymore and therefore has been removed.
The stacktrace command requires global mutable state to keep the stacktrace of the last command and there is no good place for this global state.
Similar to many popular CLI frameworks/tools, Spring Shell 4 introduce a debug mode, which when enabled, prints the stack trace of the last command right away instead of the user asking for it right after. The idea is that you configure the shell in debug mode once (like for example running Maven with -X flag) and have the stack trace of every error, instead of you having to type stacktrace every time an error occurs.
The completion command was removed and the configuration of the completions is left to the users based on their preferred shell (bash, zsh, etc).
- Deprecated APIs and annotations in v3 (namely legacy annotations
ShellComponent,ShellMethod, etc) were removed - All APIs under
org.springframework.shellin thespring-shell-coremodule were moved under theorg.springframework.shell.corepackage of the same module - All APIs under
org.springframework.shell.standardin thespring-shell-standardmodule were moved under theorg.springframework.shell.corepackage of thespring-shell-coremodule - All APIs under the
org.springframework.shell.tablein thespring-shell-tablemodule were moved under theorg.springframework.shell.jline.tui.tablepackage of thespring-shell-jlinemodule - Commands that were defined in the
spring-shell-standard-commandsmodule were moved to thespring-shell-coremodule -
EnumValueProviderwas renamed toEnumCompletionProvider -
FileValueProviderwas renamed toFileNameCompletionProvider - The following test APIs and annotations were removed:
ShellWriteSequence,ShellTypeExcludeFilter,SpringShellTestProperties,@AutoConfigureShell,@AutoConfigureShellTestClient