Skip to content

v4 migration guide

Mahmoud Ben Hassine edited this page Jan 7, 2026 · 14 revisions

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.

‼️ Please make sure to upgrade your applications to the latest Spring Shell v3.4.x before migrating to Spring Shell v4.

If a change is missing, please report it by opening an issue on GitHub.

Dependency upgrades

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.

Module changes

  • The spring-shell-core module does NOT depend on Spring Boot anymore. If you are using Spring Shell in a Spring Boot application, please add an explicit dependency to spring-shell-core-autoconfigure or one of the Spring Shell starters.
  • The spring-shell-core module does NOT depend on JLine anymore. By default, the core module uses the system's java.io.Console defined in the JDK. If you want to use the JLine console, please add an explicit dependency to spring-shell-jline.
  • The modules spring-shell-standard and spring-shell-standard-commands have been merged in the spring-shell-core module. You do not need to add any explicit dependency to these modules anymore.
  • The module spring-shell-table has been merged in spring-shell-jline. You do not need to add any explicit dependency to this module anymore if you are using JLine.
  • The module spring-shell-autoconfigure has been renamed to spring-shell-core-autoconfigure.

Declarative command definition and registration

Removal of legacy annotations

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.

Command definition

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();
   }
   
}

Command Registration

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.

Command Customization

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.

Command options

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.

Command completion

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.

Programmatic command definition and registration

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.

Shell definition

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 (requires spring-shell-jline dependency).
  • 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!");
	}

}

Shell interactivity changes

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.

Command interactivity changes

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.

Built-in commands changes

Removal of the stacktrace command

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.

Removal of the completion command

The completion command was removed and the configuration of the completions is left to the users based on their preferred shell (bash, zsh, etc).

API changes

  • Deprecated APIs and annotations in v3 (namely legacy annotations ShellComponent, ShellMethod, etc) were removed
  • All APIs under org.springframework.shell in the spring-shell-core module were moved under the org.springframework.shell.core package of the same module
  • All APIs under org.springframework.shell.standard in the spring-shell-standard module were moved under the org.springframework.shell.core package of the spring-shell-core module
  • All APIs under the org.springframework.shell.table in the spring-shell-table module were moved under the org.springframework.shell.jline.tui.table package of the spring-shell-jline module
  • Commands that were defined in the spring-shell-standard-commands module were moved to the spring-shell-core module
  • EnumValueProvider was renamed to EnumCompletionProvider
  • FileValueProvider was renamed to FileNameCompletionProvider
  • The following test APIs and annotations were removed: ShellWriteSequence, ShellTypeExcludeFilter, SpringShellTestProperties, @AutoConfigureShell, @AutoConfigureShellTestClient