Skip to content

v4 migration guide

Mahmoud Ben Hassine edited this page Dec 22, 2025 · 5 revisions

‼️ This document is a work in progress and may not cover all breaking changes as Spring Shell v4 is not GA yet.

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.

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 String hello(@Option(defaultValue = "World") String name) {
		return "Hello " + name + "!";
	}

}

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 String hello(@Option(defaultValue = "World") String name) {
        return "Hello " + name + "!";
    }

}

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.

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!");
	}

}

API changes

// TBD