Repository navigation
Annotated commands are not registered in GraalVM native binary #1229
Description
Activity
- addedstatus/need-triageTeam needs to triage and take a first lookTeam needs to triage and take a first look
on Dec 23, 2025 - removedstatus/need-triageTeam needs to triage and take a first lookTeam needs to triage and take a first look
on Dec 23, 2025 - changed the title
[-]Broken command discovery in GraalVM native binary[/-][+]Annotated commands are not registered in GraalVM native binary[/+]on Dec 28, 2025 This cannot work as the code currently does classpath scanning at runtime which native image can't do.
Irrespective of the native issue problem, I think it is a little surprising that code does classpath scanning this way. An easy fix would be to look for the presence of the annotation on beans. Doing so should probably work as
@Reflectivewill add reflection for the method to be found.I've also attached a revised version of the sample that cleans up the build: spring-shell-sample-spring-boot.zip
Reacted by Mahmoud Ben Hassine- added a commit that references this issue
on Dec 30, 2025 Hi - I have a working proof-of-concept that makes GraalVM native images work when using annotated
@Commands here. It would be some small changes to the implementation ofCommandRegistryand some new beans in the autoconfiguration to make it work.Reacted by Mahmoud Ben Hassine@snicoll Thank you for your feedback.
This cannot work as the code currently does classpath scanning at runtime which native image can't do.
That was copied from v3, but I am not sure it ever worked (I didn't get a chance to test that with v3 back then).
Irrespective of the native issue problem, I think it is a little surprising that code does classpath scanning this way. An easy fix would be to look for the presence of the annotation on beans. Doing so should probably work as @Reflective will add reflection for the method to be found.
That works. Thank you for the hint.
I will include the sample app as a test when introducing the native build (#1182).
@fmbenhassine I am little bit confused by the fix. Why are you looking for
@Componenttypes? Your commit message states " beans from the application context", but that's not what the code seems to do. Sorry if I missed the context as I read the code from GitHub's UI.If you want a reference of a component that loops over the beans of the context to check for a method annotated with a given annotation,
EventListenerMethodProcessoris a good candidate.- added a commit that references this issue
on Mar 30, 2026 The reason is that commands are expected to be defined in a class that is declared as a Spring
@Component(or any stereotype component, we have a note about it here). Thank you for the pointer, I will take a look.The reason is that commands are expected to be defined in a class that is declared as a Spring @component (or any stereotype component)
Why?
I am asking as I don't remember having seen such a pattern before. Usually, we apply a feature on a bean irrespective of how it has been contributing to the context. I've read the reference documentation about this and, as far as I understand, these are the ways you can contribute a command:
- A class that is annotated with
@Componentwith a@Command-annotated method. - A class that is referenced in
@EnabledCommand(with the same pattern) - A class that implements
Commandthat's registered as a bean.
I can see a number of issues with the current approach:
- You could annotated a class with
@Componentin a class that is not a target for classpath scanning and it wouldn't be taken into account. Reading the doc, that's not obvious, especially for beginners ("you define your methods in a @component class and the methods with specific annotations"). - You cannot expose such a bean yourself, such as:
@Bean Example1 exampleCommand() { return new Example1(); }
- However, you can do so with a
Commandimplementation, and therefore the following would work:
@Component public class MyCommand implements Command { ... }
That looks quite inconsistency to me. Besides, I can only assume that the processor for
@Commandis creating aCommandimplementation that calls the method reflectively. This is a pattern used in many places and considering all beans in the context would be probably simpler in terms of implementation. It'd probably makeEnableCommandless attractive.- A class that is annotated with
That's how the annotation programming model of Spring Shell was designed since the beginning. There were a couple specific annotations to define commands (
@ShellComponent/@ShellMethod), but they were deprecated in v3 and removed in v4, but the idea/pattern is the same in recent versions.I believe the idea of the pattern is to be able to share state between commands grouped in the same class (the typical example is a boolean flag at the class level that is flipped by two different commands like login/logout for instance)
I don't recall users having issues with this model, but I agree we need to update the docs to mention that the enclosing
@Componentclass should not be excluded from classpath scanning.That's how the annotation programming model of Spring Shell was designed since the beginning.
OK. If the argument is backward compatibility, streamlining this would increase the coverage of the feature, not decrease it.
I believe the idea of the pattern
Is "pattern" the same "pattern" I've used in my first comment? If so, I am afraid I don't see the link. My take is purely about filtering on classes annotated with
@Component. That's just a way to create beans from classpath scanning so we could argue it's an implementation detail.What makes those classes (i.e. annotated with
@Component) more special than any other bean? Why is a bean not contributed this way not taken into account?Yes, I meant pattern as in your first comment. I see your point, and that's a different story. As far as I can tell by looking at previous versions, including beans contributed in other ways was never supported. But I am open to discuss this in a different issue and improve things if possible.
For now, the fix in this issue is in line with how commands are expected to be contributed with the annotation programming model (ie a class that is annotated with
@Componenthaving@Command-annotated methods).
After fixing native runtime hints in 891a148, the native compilation now passes but the user's defined annotated commands are not discovered correctly:
Here is a minimal complete app to use with GraalVM 25+ and the snapshots of Spring Shell v4 at 891a148 or later:
spring-shell-sample-spring-boot.zip
The following instructions can be found in the README of the attached minimal project:
Once the native build is complete, run the native executable located in the
targetdirectory and see how thehellocommand does not show up:Note that this issue happens only with annotated commands, commands created programmatically with
Command.Builderand defined as beans are correctly registered:With this sample, the
hellocommand is shown and runs as expected: