Skip to content

Unexpected behavior with @Command annotationΒ #1158

Closed

Description

@vregi

Spring Boot 3.4.2
Spring Shell 3.4.0
JDK 17

Problem:

When using the @Command annotation on a component class, the Spring Shell fails to register the command, throwing an IllegalStateException with the message "No Command annotation found" in case of @EnableCommand usage and application runs without any errors, but commands are completely unregistered in case of @CommandScan usage.

Code:

CommandsNotation.java

import com.slezkipotekli.githubactivity.services.ActivityService;
import lombok.AllArgsConstructor;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.command.annotation.Option;
import org.springframework.stereotype.Component;

@Component
@AllArgsConstructor
public class CommandsNotation {
    ActivityService activityService;

    @Command(command = "github-activity")
    public String githubActivity(@Option(longNames = "username", required = false) String username ){
        return activityService.get_activity(username);
    }
}

GithubActivityApplication.java

package com.slezkipotekli.githubactivity;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.shell.command.annotation.CommandScan;
import org.springframework.shell.command.annotation.EnableCommand;

@SpringBootApplication
// @EnableCommand(CommandsNotation.class) - Included every option I used for clarity
// @CommandScan - Included every option I used for clarity
// @CommandScan("com.slezkipotekli.githubactivity") - Included every option I used for clarity
public class GithubActivityApplication {

    public static void main(String[] args) {
        SpringApplication.run(GithubActivityApplication.class, args);
    }

}

Output:

With @EnableCommand(CommandsNotation.class)

2025-02-03T21:53:21.864-05:00 ERROR 11404 --- [github-activity] [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: No Command annotation found on  'com.slezkipotekli.githubactivity.CommandsNotation'.
	at org.springframework.util.Assert.state(Assert.java:101) ~[spring-core-6.2.2.jar:6.2.2]
	at org.springframework.shell.command.annotation.support.CommandRegistrationBeanRegistrar.registerCommandClassBeanDefinition(CommandRegistrationBeanRegistrar.java:91) ~[spring-shell-core-3.4.0.jar:3.4.0]
	at org.springframework.shell.command.annotation.support.CommandRegistrationBeanRegistrar.register(CommandRegistrationBeanRegistrar.java:67) ~[spring-shell-core-3.4.0.jar:3.4.0]
	at org.springframework.shell.command.annotation.support.CommandRegistrationBeanRegistrar.register(CommandRegistrationBeanRegistrar.java:61) ~[spring-shell-core-3.4.0.jar:3.4.0]
	at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]

With @CommandScan

 :: Spring Boot ::                (v3.4.2)

2025-02-03T21:56:03.757-05:00 DEBUG 9340 --- [github-activity] [           main] o.s.s.s.StandardMethodTargetRegistrar    : Found commandBeans to register {help=org.springframework.shell.standard.commands.Help@2d7637e6, clear=org.springframework.shell.standard.commands.Clear@515b9d4a, quit=org.springframework.shell.standard.commands.Quit@39b626e5, stacktrace=org.springframework.shell.standard.commands.Stacktrace@32a4ecbe, script=org.springframework.shell.standard.commands.Script@2e71240b, historyCommand=org.springframework.shell.standard.commands.History@23cd5d42, version=org.springframework.shell.standard.commands.Version@2b44d6d0}

2025-02-03T21:56:03.769-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[help]' key='help'
2025-02-03T21:56:03.788-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with mp='method 'help' parameter 0' so='@org.springframework.shell.standard.ShellOption(optOut=false, arity=2147483647, help="The command to obtain help for.", valueProvider=org.springframework.shell.standard.CommandValueProvider.class, value={"-C", "--command"}, defaultValue="__NULL__")'
2025-02-03T21:56:03.789-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering o='-C' stripped='C'
2025-02-03T21:56:03.790-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering o='--command' stripped='command'
2025-02-03T21:56:03.790-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering longNames='[command]' shortNames='[C]'
2025-02-03T21:56:03.796-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[clear]' key='clear'
2025-02-03T21:56:03.797-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[quit, exit]' key='quit'
2025-02-03T21:56:03.798-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[stacktrace]' key='stacktrace'
2025-02-03T21:56:03.799-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[script]' key='script'
2025-02-03T21:56:03.800-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with mp='method 'script' parameter 0' so='null'
2025-02-03T21:56:03.800-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Using mp='method 'script' parameter 0' longName='file' parameterType='class java.io.File'
2025-02-03T21:56:03.801-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[history]' key='history'
2025-02-03T21:56:03.802-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with mp='method 'history' parameter 0' so='@org.springframework.shell.standard.ShellOption(optOut=false, arity=-1, help="A file to save history to.", valueProvider=org.springframework.shell.standard.ShellOption.NoValueProvider.class, value={}, defaultValue="__NULL__")'
2025-02-03T21:56:03.802-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Using mp='method 'history' parameter 0' longName='file' parameterType='class java.io.File'
2025-02-03T21:56:03.802-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering longNames='[file]' shortNames='[]'
2025-02-03T21:56:03.802-05:00 DEBUG 9340 --- [           main] o.s.s.s.StandardMethodTargetRegistrar    : Registering with keys='[version]' key='version'

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.



shell:>github-activity
No command found for 'github-activity'

Activity

  1. sourabhsmf commented on Feb 4, 2025

    @sourabhsmf

    Ran into same issue, looks like it requires @Command annotation to be present on the class as well otherwise it does not register the command. It is not very clear when reading the documentation.

  2. fmbenhassine commented on Apr 25, 2025

    @fmbenhassine
    Contributor

    I confirm the issue, the following fails with java.lang.IllegalStateException: No Command annotation found on 'demo.Main':

    package demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.shell.command.annotation.Command;
    import org.springframework.shell.command.annotation.EnableCommand;
    
    @SpringBootApplication
    @EnableCommand(Main.class)
    public class Main {
    
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    
        @Command(command = "example")
        public String example() {
            return "Hello";
        }
    
    }

    And the following does not register the command as documented:

    package demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.shell.command.annotation.Command;
    import org.springframework.shell.command.annotation.CommandScan;
    
    @SpringBootApplication
    @CommandScan
    public class Main {
    
        public static void main(String[] args) {
            SpringApplication.run(Main.class, args);
        }
    
        @Command(command = "example")
        public String example() {
            return "Hello";
        }
    
    }

    To me, @Command should only target methods, not types. It is confusing to have to annotate a class with @Command and also command methods with @Command as well. I believe the main reason for that was to have parent/child relationship where one can define common attributes for all commands at the class level, but this should be done with another annotation or some other way. The second confusing behaviour is that some attributes have different meaning / behaviour depending on whether the annotation is present on a class or a method, like aliases for example.

    I believe this should be revisited and fixed, so I will plan it for the next major release.

  3. added and removed
    status/need-triageTeam needs to triage and take a first look
    on Apr 25, 2025
  4. added this to the 4.0.0 milestone on Apr 25, 2025
  5. modified the milestones: 4.0.0, 4.0.0-M2 on Oct 31, 2025
  6. added a commit that references this issue on Oct 31, 2025
  7. fmbenhassine commented on Nov 2, 2025

    @fmbenhassine
    Contributor

    Related issue: #1206

  8. fmbenhassine commented on Dec 9, 2025

    @fmbenhassine
    Contributor

    Resolved in 4.0.0-M2.

    @EnableCommand is now only needed when using Spring Shell without Spring Boot. @CommandScan is not needed anymore. New samples with and without Spring Boot have been added to get started:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions