🕷️ Crawler Inspector

URL Lookup

Direct Parameter Lookup

Raw Queries and Responses

1. Shard Calculation

Query:
Response:
Calculated Shard: 144 (from laksa042)

2. Crawled Status Check

Query:
Response:

3. Robots.txt Check

Query:
Response:

4. Spam/Ban Check

Query:
Response:

5. Seen Status Check

ℹ️ Skipped - page is already crawled

đź“„
INDEXABLE
âś…
CRAWLED
3 days ago
🤖
ROBOTS ALLOWED

Page Info Filters

FilterStatusConditionDetails
HTTP statusPASSdownload_http_code = 200HTTP 200
Age cutoffPASSdownload_stamp > now() - 6 MONTH0.1 months ago
History dropPASSisNull(history_drop_reason)No drop reason
Spam/banPASSfh_dont_index != 1 AND ml_spam_score = 0ml_spam_score=0
CanonicalPASSmeta_canonical IS NULL OR = '' OR = src_unparsedNot set

Page Details

PropertyValue
URLhttps://www.baeldung.com/java-picocli-create-command-line-program
Last Crawled2026-04-03 06:04:19 (3 days ago)
First Indexed2019-05-11 17:33:50 (6 years ago)
HTTP Status Code200
Meta TitleCreate a Java Command Line Program with Picocli | Baeldung
Meta DescriptionLearn how to use Picocli to easily create command line programs in Java.
Meta Canonicalnull
Boilerpipe Text
1. Introduction In this tutorial, we’ll approach the picocli library , which allows us to easily create command line programs in Java. We’ll first get started by creating a Hello World command. We’ll then take a deep dive into the key features of the library by reproducing, partially, the git  command. 2. Hello World Command Let’s begin with something easy: a Hello World command! First things first, we need to add the dependency to the  picocli project : < dependency > < groupId > info.picocli </ groupId > < artifactId > picocli </ artifactId > < version > 4.7.0 </ version > </ dependency > As we can see, we’ll use the 4.7.0 version of the library. Now that the dependency is set up, let’s create our Hello World command. In order to do that, we’ll use the @Command annotation from the library : @Command( name = "hello", description = "Says hello" ) public class HelloWorldCommand { } As we can see, the annotation can take parameters. We’re only using two of them here. Their purpose is to provide information about the current command and text for the automatic help message. At the moment, there’s not much we can do with this command. To make it do something, we need to add a main  method calling the convenience CommandLine.run(Runnable, String[]) method . This takes two parameters: an instance of our command, which thus has to implement the Runnable interface, and a String array representing the command arguments (options, parameters, and subcommands): public class HelloWorldCommand implements Runnable { public static void main (String[] args) { CommandLine.run( new HelloWorldCommand (), args); } @Override public void run () { System.out.println( "Hello World!" ); } } Now, when we run the main method, we’ll see that the console outputs “Hello World!” When packaged to a jar , we can run our Hello World command using the java command: java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand With no surprise, that also outputs the “Hello World!” string to the console. 3. A Concrete Use Case Now that we’ve seen the basics, we’ll deep dive into the  picocli library. In order to do that, we’re going to reproduce, partially, a popular command: git . Of course, the purpose won’t be to implement the git command behavior but to reproduce the possibilities of the git command — which subcommands exist and which options are available for a peculiar subcommand. First, we have to create a GitCommand class as we did for our Hello World command: @Command public class GitCommand implements Runnable { public static void main (String[] args) { CommandLine.run( new GitCommand (), args); } @Override public void run () { System.out.println( "The popular git command" ); } } 4. Adding Subcommands The git  command offers a lot of subcommands — add, commit, remote , and many more. We’ll focus here on add and  commit . So, our goal here will be to declare those two subcommands to the main command.  Picocli offers three ways to achieve this. 4.1. Using the @Command Annotation on Classes The @Command annotation offers the possibility to register subcommands through the  subcommands parameter : @Command( subcommands = { GitAddCommand.class, GitCommitCommand.class } ) In our case, we add two new classes:  GitAddCommand and  GitCommitCommand . Both are annotated with @Command and implement  Runnable . It’s important to give them a name, as the names will be used by picocli to recognize which subcommand(s) to execute: @Command( name = "add" ) public class GitAddCommand implements Runnable { @Override public void run () { System.out.println( "Adding some files to the staging area" ); } } @Command( name = "commit" ) public class GitCommitCommand implements Runnable { @Override public void run () { System.out.println( "Committing files in the staging area, how wonderful?" ); } } Thus, if we run our main command with  add as an argument, the console will output “Adding some files to the staging area” . 4.2. Using the @Command Annotation on Methods Another way to declare subcommands is to create @Command -annotated methods representing those commands in the GitCommand class : @Command(name = "add") public void addCommand () { System.out.println( "Adding some files to the staging area" ); } @Command(name = "commit") public void commitCommand () { System.out.println( "Committing files in the staging area, how wonderful?" ); } That way, we can directly implement our business logic into the methods and not create separate classes to handle it. 4.3. Adding Subcommands Programmatically Finally,  picocli offers us the possibility to register our subcommands programmatically. This one’s a bit trickier, as we have to create a CommandLine object wrapping our command and then add the subcommands to it: CommandLine commandLine = new CommandLine ( new GitCommand ()); commandLine.addSubcommand( "add" , new GitAddCommand ()); commandLine.addSubcommand( "commit" , new GitCommitCommand ()); After that, we still have to run our command, but we can’t make use of the  CommandLine.run() method anymore . Now, we have to call the  parseWithHandler() method on our newly created C ommandLine object: commandLine.parseWithHandler( new RunLast (), args); We should note the use of the  RunLast class, which tells picocli to run the most specific subcommand. There are two other command handlers provided by picocli : RunFirst and  RunAll . The former runs the topmost command, while the latter runs all of them. When using the convenience method  CommandLine.run() , the  RunLast handler is used by default. 5. Managing Options Using the @Option Annotation 5.1. Option with No Argument Let’s now see how to add some options to our commands. Indeed, we would like to tell our  add command that it should add all modified files. To achieve that, we’ll add a field annotated with the @Option annotation to our GitAddCommand class: @Option(names = {"-A", "--all"}) private boolean allFiles; @Override public void run () { if (allFiles) { System.out.println( "Adding all files to the staging area" ); } else { System.out.println( "Adding some files to the staging area" ); } } As we can see, the annotation takes a names parameter, which gives the different names of the option. Therefore, calling the add command with either -A or  –all will set the  allFiles field to  true . So, if we run the command with the option, the console will show “Adding all files to the staging area” . 5.2. Option with an Argument As we just saw, for options without arguments, their presence or absence is always evaluated to a boolean value. However, it’s possible to register options that take arguments. We can do this simply by declaring our field to be of a different type. Let’s add a message option to our  commit command: @Option(names = {"-m", "--message"}) private String message; @Override public void run () { System.out.println( "Committing files in the staging area, how wonderful?" ); if (message != null ) { System.out.println( "The commit message is " + message); } } Unsurprisingly, when given the  message option, the command will show the commit message on the console. Later in the article, we’ll cover which types are handled by the library and how to handle other types. 5.3. Option with Multiple Arguments But now, what if we want our command to take multiple messages, as is done with the real git commit command? No worries, let’s make our field be an array or a Collection , and we’re pretty much done: @Option(names = {"-m", "--message"}) private String[] messages; @Override public void run () { System.out.println( "Committing files in the staging area, how wonderful?" ); if (messages != null ) { System.out.println( "The commit message is" ); for (String message : messages) { System.out.println(message); } } } Now, we can use the message option multiple times: commit -m "My commit is great" -m "My commit is beautiful" However, we might also want to give the option only once and separate the different parameters by a regex delimiter. Hence, we can use the split parameter of the  @Option annotation: @Option(names = {"-m", "--message"}, split = ",") private String[] messages; Now, we can pass  -m “My commit is great”,”My commit is beautiful” to achieve the same result as above. 5.4. Required Option Sometimes, we might have an option that is required. The required argument, which defaults to  false , allows us to do that: @Option(names = {"-m", "--message"}, required = true) private String[] messages; Now it’s impossible to call the  commit command without specifying the  message option. If we try to do that,  picocli will print an error: Missing required option '--message=<messages>' Usage: git commit -m=<messages> [-m=<messages>]... -m, --message=<messages> 6. Managing Positional Parameters 6.1. Capture Positional Parameters Now, let’s focus on our  add command because it’s not very powerful yet. We can only decide to add all files, but what if we wanted to add specific files? We could use another option to do that, but a better choice here would be to use positional parameters. Indeed, positional parameters are meant to capture command arguments that occupy specific positions and are neither subcommands nor options. In our example, this would enable us to do something like: add file1 file2 In order to capture positional parameters, we’ll make use of the @Parameters annotation : @Parameters private List<Path> files; @Override public void run () { if (allFiles) { System.out.println( "Adding all files to the staging area" ); } if (files != null ) { files.forEach(path -> System.out.println( "Adding " + path + " to the staging area" )); } } Now, our command from earlier would print: Adding file1 to the staging area Adding file2 to the staging area 6.2. Capture a Subset of Positional Parameters It’s possible to be more fine-grained about which positional parameters to capture, thanks to the  index parameter of the annotation. The index is zero-based. Thus, if we define: @Parameters(index="2..*") This would capture arguments that don’t match options or subcommands, from the third one to the end. The index can be either a range or a single number, representing a single position. 7. A Word About Type Conversion As we’ve seen earlier in this tutorial, picocli handles some type conversion by itself. For example, it maps multiple values to arrays or  Collections , but it can also map arguments to specific types like when we use the Path class for the add command. As a matter of fact,  picocli comes with a bunch of pre-handled types . This means we can use those types directly without having to think about converting them ourselves. However, we might need to map our command arguments to types other than those that are already handled. Fortunately for us, this is possible thanks to the  ITypeConverter interface and the  CommandLine#registerConverter method, which associates a type to a converter . Let’s imagine we want to add the  config subcommand to our  git command, but we don’t want users to change a configuration element that doesn’t exist. So, we decide to map those elements to an enum: public enum ConfigElement { USERNAME( "user.name" ), EMAIL( "user.email" ); private final String value; ConfigElement(String value) { this .value = value; } public String value () { return value; } public static ConfigElement from (String value) { return Arrays.stream(values()) .filter(element -> element.value.equals(value)) .findFirst() .orElseThrow(() -> new IllegalArgumentException ( "The argument " + value + " doesn't match any ConfigElement" )); } } Plus, in our newly created  GitConfigCommand class, let’s add two positional parameters: @Parameters(index = "0") private ConfigElement element; @Parameters(index = "1") private String value; @Override public void run () { System.out.println( "Setting " + element.value() + " to " + value); } This way, we make sure that users won’t be able to change non-existent configuration elements. Finally, we have to register our converter. What’s beautiful is that, if using Java 8 or higher, we don’t even have to create a class implementing the  ITypeConverter interface. We can just pass a lambda or method reference to the registerConverter() method: CommandLine commandLine = new CommandLine ( new GitCommand ()); commandLine.registerConverter(ConfigElement.class, ConfigElement::from); commandLine.parseWithHandler( new RunLast (), args); This happens in the  GitCommand  main() method. Note that we had to let go of the convenience  CommandLine.run() method. When used with an unhandled configuration element, the command would show the help message plus a piece of information telling us that it wasn’t possible to convert the parameter to a ConfigElement : Invalid value for positional parameter at index 0 (<element>): cannot convert 'user.phone' to ConfigElement (java.lang.IllegalArgumentException: The argument user.phone doesn't match any ConfigElement) Usage: git config <element> <value> <element> <value> 8. Integrating with Spring Boot Finally, let’s see how to Springify all that! Indeed, we might be working within a Spring Boot environment and want to benefit from it in our command-line program. In order to do that, we must create a  SpringBootApplication   implementing the  CommandLineRunner interface : @SpringBootApplication public class Application implements CommandLineRunner { public static void main (String[] args) { SpringApplication.run(Application.class, args); } @Override public void run (String... args) { } } Plus, let’s annotate all our commands and subcommands with the Spring @Component annotation and autowire all that in our  Application : private GitCommand gitCommand; private GitAddCommand addCommand; private GitCommitCommand commitCommand; private GitConfigCommand configCommand; public Application (GitCommand gitCommand, GitAddCommand addCommand, GitCommitCommand commitCommand, GitConfigCommand configCommand) { this .gitCommand = gitCommand; this .addCommand = addCommand; this .commitCommand = commitCommand; this .configCommand = configCommand; } Note that we had to autowire every subcommand. Unfortunately, this is because, for now, picocli is not yet able to retrieve subcommands from the Spring context when declared declaratively (with annotations). Thus, we’ll have to do that wiring ourselves, in a programmatic way: @Override public void run (String... args) { CommandLine commandLine = new CommandLine (gitCommand); commandLine.addSubcommand( "add" , addCommand); commandLine.addSubcommand( "commit" , commitCommand); commandLine.addSubcommand( "config" , configCommand); commandLine.parseWithHandler( new CommandLine .RunLast(), args); } And now, our command line program works like a charm with Spring components. Therefore, we could create some service classes and use them in our commands, and let Spring take care of the dependency injection. 9. Conclusion In this article, we’ve seen some key features of the  picocli  library. We’ve learned how to create a new command and add some subcommands to it. We’ve seen many ways to deal with options and positional parameters. Plus, we’ve learned how to implement our own type converters to make our commands strongly typed. Finally, we’ve seen how to bring Spring Boot into our commands. Of course, there are many things more to discover about it. The library provides complete documentation . The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member , start learning and coding on the project.
Markdown
![](https://www.facebook.com/tr?id=512471148948613&ev=PageView&noscript=1) [![The Baeldung logo](https://www.baeldung.com/wp-content/themes/baeldung/icon/logo.svg)](https://www.baeldung.com/ "Baeldung") - [![The Baeldung Logo](https://www.baeldung.com/wp-content/themes/baeldung/icon/logo.svg)](https://www.baeldung.com/ "Baeldung") - [Start Here](https://www.baeldung.com/start-here) - [Spring Courses ▼▲]() - [REST with Spring Boot The canonical reference for building a production grade API with Spring](https://www.baeldung.com/courses/rest-with-spring-boot-course "The “REST With Spring” Course") - [Learn Spring Security THE unique Spring Security education if you’re working with Java today](https://www.baeldung.com/courses/learn-spring-security-course) - [Learn Spring From no experience to actually building stuff​](https://www.baeldung.com/courses/learn-spring-course) - [Learn Spring Data JPA The full guide to persistence with Spring Data JPA](https://www.baeldung.com/courses/learn-spring-data-jpa-course) - [View All Spring Courses](https://www.baeldung.com/members/all-courses) - [Java Courses ▼▲]() - [Learn JUnit Master the most popular Java testing framework.](https://www.baeldung.com/courses/learn-junit-course) - [Learn Maven Simplify Your Build with Apache Maven](https://www.baeldung.com/courses/learn-maven-course) - [Learn Hibernate JPA Master persistence with the Java specification and Hibernate implementation](https://www.baeldung.com/courses/learn-hibernate-jpa-course) - [Learn Mockito Enhance your unit tests through practical mocking](https://www.baeldung.com/courses/learn-mockito-course) - [View All Courses](https://www.baeldung.com/members/all-courses) - [Pricing](https://www.baeldung.com/pricing) - [About ▼▲]() - [Full Archive The high level overview of all the articles on the site.](https://www.baeldung.com/full_archive) - [Baeldung Ebooks Discover all of our eBooks](https://www.baeldung.com/library/) - [About Baeldung About Baeldung.](https://www.baeldung.com/about) # Create a Java Command Line Program with Picocli Last updated: January 11, 2024 ![](https://www.baeldung.com/wp-content/uploads/custom_avatars/Francois-Dupire-150x150.jpeg) Written by: [François Dupire](https://www.baeldung.com/author/francois-dupire "Posts by François Dupire") - [Java](https://www.baeldung.com/category/java) - [CLI](https://www.baeldung.com/tag/cli) Course – Spring Sale 2026 – NPI (cat=Baeldung) ![announcement - icon](https://www.baeldung.com/wp-content/uploads/2022/04/announcement-icon.png) Yes, we're now running our Spring Sale. All Courses are **30% off** until **31st March, 2026** **[\>\> EXPLORE ACCESS NOW](https://www.baeldung.com/Spring-Sale-2026-NPI-2-t5zq)** ## 1\. Introduction In this tutorial, we’ll approach the [*picocli* library](https://picocli.info/), which allows us to easily create command line programs in Java. We’ll first get started by creating a Hello World command. We’ll then take a deep dive into the key features of the library by reproducing, partially, the *git* command. ## 2\. Hello World Command Let’s begin with something easy: a Hello World command\! First things first, we need to add the [dependency to the *picocli* project](https://mvnrepository.com/artifact/info.picocli/picocli): ``` Copy ``` As we can see, we’ll use the *4\.7.0* version of the library. Now that the dependency is set up, let’s create our Hello World command. In order to do that, **we’ll use the *@Command* annotation from the library**: ``` Copy ``` As we can see, the annotation can take parameters. We’re only using two of them here. Their purpose is to provide information about the current command and text for the automatic help message. At the moment, there’s not much we can do with this command. To make it do something, we need to add a *main* method calling **the convenience *CommandLine.run(Runnable, String\[\])* method**. This takes two parameters: an instance of our command, which thus has to implement the *Runnable* interface, and a *String* array representing the command arguments (options, parameters, and subcommands): ``` Copy ``` Now, when we run the *main* method, we’ll see that the console outputs *“Hello World!”* [When packaged to a jar](https://www.baeldung.com/java-create-jar), we can run our Hello World command using the *java* command: ``` java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommandCopy ``` With no surprise, that also outputs the *“Hello World!”* string to the console. ## 3\. A Concrete Use Case Now that we’ve seen the basics, we’ll deep dive into the *picocli* library. In order to do that, we’re going to reproduce, partially, a popular command: *git*. Of course, the purpose won’t be to implement the *git* command behavior but to reproduce the possibilities of the *git* command — which subcommands exist and which options are available for a peculiar subcommand. First, we have to create a *GitCommand* class as we did for our Hello World command: ``` Copy ``` ## 4\. Adding Subcommands The *git* command offers a lot of [subcommands](https://picocli.info/#_subcommands) — *add, commit, remote*, and many more. We’ll focus here on *add* and *commit*. So, our goal here will be to declare those two subcommands to the main command. *Picocli* offers three ways to achieve this. ### 4\.1. Using the *@Command* Annotation on Classes **The *@Command* annotation offers the possibility to register subcommands through the *subcommands* parameter**: ``` Copy ``` In our case, we add two new classes: *GitAddCommand* and *GitCommitCommand*. Both are annotated with *@Command* and implement *Runnable*. **It’s important to give them a name, as the names will be used by *picocli* to recognize which subcommand(s) to execute:** ``` Copy ``` ``` Copy ``` Thus, if we run our main command with *add* as an argument, the console will output *“Adding some files to the staging area”*. ### 4\.2. Using the *@Command* Annotation on Methods Another way to declare subcommands is to **create *@Command*\-annotated methods representing those commands in the *GitCommand* class**: ``` Copy ``` That way, we can directly implement our business logic into the methods and not create separate classes to handle it. ### 4\.3. Adding Subcommands Programmatically **Finally, *picocli* offers us the possibility to register our subcommands programmatically.** This one’s a bit trickier, as we have to create a *CommandLine* object wrapping our command and then add the subcommands to it: ``` Copy ``` After that, we still have to run our command, but **we can’t make use of the *CommandLine.run()* method anymore**. Now, we have to call the *parseWithHandler()* method on our newly created C*ommandLine* object: ``` commandLine.parseWithHandler(new RunLast(), args);Copy ``` We should note the use of the *RunLast* class, which tells *picocli* to run the most specific subcommand. There are two other command handlers provided by *picocli*: *RunFirst* and *RunAll*. The former runs the topmost command, while the latter runs all of them. When using the convenience method *CommandLine.run()*, the *RunLast* handler is used by default. ## 5\. Managing Options Using the *@Option* Annotation ### 5\.1. Option with No Argument Let’s now see how to add some options to our commands. Indeed, we would like to tell our *add* command that it should add all modified files. To achieve that, **we’ll add a field annotated with the [*@Option*](https://picocli.info/#_options) annotation** to our *GitAddCommand* class: ``` Copy ``` As we can see, the annotation takes a *names* parameter, which gives the different names of the option. Therefore, calling the *add* command with either *\-A* or *–all* will set the *allFiles* field to *true*. So, if we run the command with the option, the console will show *“Adding all files to the staging area”*. ### 5\.2. Option with an Argument As we just saw, for options without arguments, their presence or absence is always evaluated to a *boolean* value. However, it’s possible to register options that take arguments. **We can do this simply by declaring our field to be of a different type.** Let’s add a *message* option to our *commit* command: ``` Copy ``` Unsurprisingly, when given the *message* option, the command will show the commit message on the console. Later in the article, we’ll cover which types are handled by the library and how to handle other types. ### 5\.3. Option with Multiple Arguments But now, what if we want our command to take multiple messages, as is done with the real [*git commit*](https://git-scm.com/docs/git-commit) command? No worries, **let’s make our field be an *array* or a *Collection***, and we’re pretty much done: ``` Copy ``` Now, we can use the *message* option multiple times: ``` commit -m "My commit is great" -m "My commit is beautiful"Copy ``` However, we might also want to give the option only once and separate the different parameters by a regex delimiter. Hence, we can use the *split* parameter of the *@Option* annotation: ``` Copy ``` Now, we can pass *\-m “My commit is great”,”My commit is beautiful”* to achieve the same result as above. ### 5\.4. Required Option **Sometimes, we might have an option that is required. The *required* argument, which defaults to *false*, allows us to do that:** ``` Copy ``` Now it’s impossible to call the *commit* command without specifying the *message* option. If we try to do that, *picocli* will print an error: ``` Copy ``` ## 6\. Managing Positional Parameters ### 6\.1. Capture Positional Parameters Now, let’s focus on our *add* command because it’s not very powerful yet. We can only decide to add all files, but what if we wanted to add specific files? We could use another option to do that, but a better choice here would be to use positional parameters. Indeed, **positional parameters are meant to capture command arguments that occupy specific positions and are neither subcommands nor options.** In our example, this would enable us to do something like: ``` add file1 file2Copy ``` In order to capture positional parameters, **we’ll make use of the [*@Parameters*](https://picocli.info/#_positional_parameters) annotation**: ``` Copy ``` Now, our command from earlier would print: ``` Copy ``` ### 6\.2. Capture a Subset of Positional Parameters It’s possible to be more fine-grained about which positional parameters to capture, thanks to the *index* parameter of the annotation. The index is zero-based. Thus, if we define: ``` @Parameters(index="2..*")Copy ``` This would capture arguments that don’t match options or subcommands, from the third one to the end. The index can be either a range or a single number, representing a single position. ## 7\. A Word About Type Conversion As we’ve seen earlier in this tutorial, *picocli* handles some type conversion by itself. For example, it maps multiple values to *arrays* or *Collections*, but it can also map arguments to specific types like when we use the *Path* class for the *add* command. **As a matter of fact, *picocli* comes with [a bunch of pre-handled types](https://picocli.info/#_built_in_types). This means we can use those types directly without having to think about converting them ourselves.** However, we might need to map our command arguments to types other than those that are already handled. Fortunately for us, **this is possible thanks to the [*ITypeConverter*](https://picocli.info/#_custom_type_converters) interface and the *CommandLine\#registerConverter* method, which associates a type to a converter**. Let’s imagine we want to add the *config* subcommand to our *git* command, but we don’t want users to change a configuration element that doesn’t exist. So, we decide to map those elements to an enum: ``` Copy ``` Plus, in our newly created *GitConfigCommand* class, let’s add two positional parameters: ``` Copy ``` This way, we make sure that users won’t be able to change non-existent configuration elements. Finally, we have to register our converter. What’s beautiful is that, if using Java 8 or higher, we don’t even have to create a class implementing the *ITypeConverter* interface. **We can just pass a lambda or method reference to the *registerConverter()* method:** ``` Copy ``` This happens in the *GitCommand* *main()* method. Note that we had to let go of the convenience *CommandLine.run()* method. When used with an unhandled configuration element, the command would show the help message plus a piece of information telling us that it wasn’t possible to convert the parameter to a *ConfigElement*: ``` Copy ``` ## 8\. Integrating with Spring Boot Finally, let’s see how to Springify all that\! Indeed, we might be working within a Spring Boot environment and want to benefit from it in our command-line program. In order to do that, **we must create a *SpringBootApplication*implementing the *CommandLineRunner* interface**: ``` Copy ``` Plus, **let’s annotate all our commands and subcommands with the Spring *@Component* annotation** and autowire all that in our *Application*: ``` Copy ``` Note that we had to autowire every subcommand. Unfortunately, this is because, for now, *picocli* is not yet able to retrieve subcommands from the Spring context when declared declaratively (with annotations). Thus, we’ll have to do that wiring ourselves, in a programmatic way: ``` Copy ``` And now, our command line program works like a charm with Spring components. Therefore, we could create some service classes and use them in our commands, and let Spring take care of the dependency injection. ## 9\. Conclusion In this article, we’ve seen some key features of the *picocli* library. We’ve learned how to create a new command and add some subcommands to it. We’ve seen many ways to deal with options and positional parameters. Plus, we’ve learned how to implement our own type converters to make our commands strongly typed. Finally, we’ve seen how to bring Spring Boot into our commands. Of course, there are many things more to discover about it. The library provides [complete documentation](https://picocli.info/). The code backing this article is available on GitHub. Once you're **logged in as a [Baeldung Pro Member](https://www.baeldung.com/members/)**, start learning and coding on the project. Course – Spring Sale 2026 – NPI (All) ![announcement - icon](https://www.baeldung.com/wp-content/uploads/2022/04/announcement-icon.png) Yes, we're now running our Spring Sale. All Courses are **30% off** until **31st March, 2026** **[\>\> EXPLORE ACCESS NOW](https://www.baeldung.com/Spring-Sale-2026-NPI-3-zlnp)** Course – Spring Sale 2026 – NPI (All) [![opt-in](https://www.baeldung.com/wp-content/uploads/2026/03/all-courses-widget-spring-sale-2026.png)](https://www.baeldung.com/Spring-Sale-2026-NPI-1-8l1j) ![The Baeldung logo](https://www.baeldung.com/wp-content/themes/baeldung/icon/logo.svg) #### Courses - [All Courses](https://www.baeldung.com/courses/all-courses) - [Baeldung All Access](https://www.baeldung.com/courses/all-access) - [Baeldung All Team Access](https://www.baeldung.com/courses/all-access-team) - [Login Course Platform](https://www.baeldung.com/members/account) #### Series - [Java “Back to Basics” Tutorial](https://www.baeldung.com/java-tutorial) - [Learn Spring Boot Series](https://www.baeldung.com/spring-boot) - [Spring Tutorial](https://www.baeldung.com/spring-tutorial) - [Get Started with Java](https://www.baeldung.com/get-started-with-java-series) - [Spring Framework Introduction](https://www.baeldung.com/spring-intro) - [All About String in Java](https://www.baeldung.com/java-string) - [Security with Spring](https://www.baeldung.com/security-spring) #### About - [About Baeldung](https://www.baeldung.com/about) - [The Full Archive](https://www.baeldung.com/full_archive) - [Editors](https://www.baeldung.com/editors) - [Our Partners](https://www.baeldung.com/partners/) - [Partner with Baeldung](https://www.baeldung.com/partners/work-with-us) - [eBooks](https://www.baeldung.com/library/) - [FAQ](https://www.baeldung.com/library/faq) - [![](https://www.baeldung.com/wp-content/uploads/2024/09/pro-icon.svg)Baeldung Pro](https://www.baeldung.com/members/) - [Terms of Service](https://www.baeldung.com/terms-of-service) - [Privacy Policy](https://www.baeldung.com/privacy-policy) - [Company Info](https://www.baeldung.com/baeldung-company-info) - [Contact](https://www.baeldung.com/contact) Privacy Manager ![The Baeldung Logo](https://www.baeldung.com/wp-content/themes/baeldung/icon/whiteleaf.svg) Follow the Java Category ![](https://www.baeldung.com/wp-content/uploads/2017/07/follow-the-spring.png) Follow the Java category to get regular info about the new articles and tutorials we publish here. ![](https://www.baeldung.com/wp-content/themes/baeldung/icon/logo.svg) ## Looks like your ad blocker is on. × We rely on ads to keep creating quality content for you to enjoy for free. Please support our site by disabling your ad blocker or, use **Baeldung Pro** for a clean, absolutely **no-ads** reading experience. Disable [Baeldung Pro![](https://www.baeldung.com/wp-content/themes/baeldung/icon/pro-icon.svg)](https://www.baeldung.com/members) Continue without supporting us #### Choose your Ad Blocker - Adblock Plus - Adblock - Adguard - Ad Remover - Brave - Ghostery - uBlock Origin - uBlock - UltraBlock - Other 1. In the extension bar, click the AdBlock Plus icon 2. Click the large blue toggle for this website 3. Click refresh 1. In the extension bar, click the AdBlock icon 2. Under "Pause on this site" click "Always" 1. In the extension bar, click on the Adguard icon 2. Click on the large green toggle for this website 1. In the extension bar, click on the Ad Remover icon 2. Click "Disable on This Website" 1. In the extension bar, click on the orange lion icon 2. Click the toggle on the top right, shifting from "Up" to "Down" 1. In the extension bar, click on the Ghostery icon 2. Click the "Anti-Tracking" shield so it says "Off" 3. Click the "Ad-Blocking" stop sign so it says "Off" 4. Refresh the page 1. In the extension bar, click on the uBlock Origin icon 2. Click on the big, blue power button 3. Refresh the page 1. In the extension bar, click on the uBlock icon 2. Click on the big, blue power button 3. Refresh the page 1. In the extension bar, click on the UltraBlock icon 2. Check the "Disable UltraBlock" checkbox 1. Please disable your Ad Blocker Go Back
Readable Markdown
## 1\. Introduction In this tutorial, we’ll approach the [*picocli* library](https://picocli.info/), which allows us to easily create command line programs in Java. We’ll first get started by creating a Hello World command. We’ll then take a deep dive into the key features of the library by reproducing, partially, the *git* command. ## 2\. Hello World Command Let’s begin with something easy: a Hello World command\! First things first, we need to add the [dependency to the *picocli* project](https://mvnrepository.com/artifact/info.picocli/picocli): ``` <dependency> <groupId>info.picocli</groupId> <artifactId>picocli</artifactId> <version>4.7.0</version> </dependency> ``` As we can see, we’ll use the *4\.7.0* version of the library. Now that the dependency is set up, let’s create our Hello World command. In order to do that, **we’ll use the *@Command* annotation from the library**: ``` @Command( name = "hello", description = "Says hello" ) public class HelloWorldCommand { } ``` As we can see, the annotation can take parameters. We’re only using two of them here. Their purpose is to provide information about the current command and text for the automatic help message. At the moment, there’s not much we can do with this command. To make it do something, we need to add a *main* method calling **the convenience *CommandLine.run(Runnable, String\[\])* method**. This takes two parameters: an instance of our command, which thus has to implement the *Runnable* interface, and a *String* array representing the command arguments (options, parameters, and subcommands): ``` public class HelloWorldCommand implements Runnable { public static void main(String[] args) { CommandLine.run(new HelloWorldCommand(), args); } @Override public void run() { System.out.println("Hello World!"); } } ``` Now, when we run the *main* method, we’ll see that the console outputs *“Hello World!”* [When packaged to a jar](https://www.baeldung.com/java-create-jar), we can run our Hello World command using the *java* command: ``` java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand ``` With no surprise, that also outputs the *“Hello World!”* string to the console. ## 3\. A Concrete Use Case Now that we’ve seen the basics, we’ll deep dive into the *picocli* library. In order to do that, we’re going to reproduce, partially, a popular command: *git*. Of course, the purpose won’t be to implement the *git* command behavior but to reproduce the possibilities of the *git* command — which subcommands exist and which options are available for a peculiar subcommand. First, we have to create a *GitCommand* class as we did for our Hello World command: ``` @Command public class GitCommand implements Runnable { public static void main(String[] args) { CommandLine.run(new GitCommand(), args); } @Override public void run() { System.out.println("The popular git command"); } } ``` ## 4\. Adding Subcommands The *git* command offers a lot of [subcommands](https://picocli.info/#_subcommands) — *add, commit, remote*, and many more. We’ll focus here on *add* and *commit*. So, our goal here will be to declare those two subcommands to the main command. *Picocli* offers three ways to achieve this. ### 4\.1. Using the *@Command* Annotation on Classes **The *@Command* annotation offers the possibility to register subcommands through the *subcommands* parameter**: ``` @Command( subcommands = { GitAddCommand.class, GitCommitCommand.class } ) ``` In our case, we add two new classes: *GitAddCommand* and *GitCommitCommand*. Both are annotated with *@Command* and implement *Runnable*. **It’s important to give them a name, as the names will be used by *picocli* to recognize which subcommand(s) to execute:** ``` @Command( name = "add" ) public class GitAddCommand implements Runnable { @Override public void run() { System.out.println("Adding some files to the staging area"); } } ``` ``` @Command( name = "commit" ) public class GitCommitCommand implements Runnable { @Override public void run() { System.out.println("Committing files in the staging area, how wonderful?"); } } ``` Thus, if we run our main command with *add* as an argument, the console will output *“Adding some files to the staging area”*. ### 4\.2. Using the *@Command* Annotation on Methods Another way to declare subcommands is to **create *@Command*\-annotated methods representing those commands in the *GitCommand* class**: ``` @Command(name = "add") public void addCommand() { System.out.println("Adding some files to the staging area"); } @Command(name = "commit") public void commitCommand() { System.out.println("Committing files in the staging area, how wonderful?"); } ``` That way, we can directly implement our business logic into the methods and not create separate classes to handle it. ### 4\.3. Adding Subcommands Programmatically **Finally, *picocli* offers us the possibility to register our subcommands programmatically.** This one’s a bit trickier, as we have to create a *CommandLine* object wrapping our command and then add the subcommands to it: ``` CommandLine commandLine = new CommandLine(new GitCommand()); commandLine.addSubcommand("add", new GitAddCommand()); commandLine.addSubcommand("commit", new GitCommitCommand()); ``` After that, we still have to run our command, but **we can’t make use of the *CommandLine.run()* method anymore**. Now, we have to call the *parseWithHandler()* method on our newly created C*ommandLine* object: ``` commandLine.parseWithHandler(new RunLast(), args); ``` We should note the use of the *RunLast* class, which tells *picocli* to run the most specific subcommand. There are two other command handlers provided by *picocli*: *RunFirst* and *RunAll*. The former runs the topmost command, while the latter runs all of them. When using the convenience method *CommandLine.run()*, the *RunLast* handler is used by default. ## 5\. Managing Options Using the *@Option* Annotation ### 5\.1. Option with No Argument Let’s now see how to add some options to our commands. Indeed, we would like to tell our *add* command that it should add all modified files. To achieve that, **we’ll add a field annotated with the [*@Option*](https://picocli.info/#_options) annotation** to our *GitAddCommand* class: ``` @Option(names = {"-A", "--all"}) private boolean allFiles; @Override public void run() { if (allFiles) { System.out.println("Adding all files to the staging area"); } else { System.out.println("Adding some files to the staging area"); } } ``` As we can see, the annotation takes a *names* parameter, which gives the different names of the option. Therefore, calling the *add* command with either *\-A* or *–all* will set the *allFiles* field to *true*. So, if we run the command with the option, the console will show *“Adding all files to the staging area”*. ### 5\.2. Option with an Argument As we just saw, for options without arguments, their presence or absence is always evaluated to a *boolean* value. However, it’s possible to register options that take arguments. **We can do this simply by declaring our field to be of a different type.** Let’s add a *message* option to our *commit* command: ``` @Option(names = {"-m", "--message"}) private String message; @Override public void run() { System.out.println("Committing files in the staging area, how wonderful?"); if (message != null) { System.out.println("The commit message is " + message); } } ``` Unsurprisingly, when given the *message* option, the command will show the commit message on the console. Later in the article, we’ll cover which types are handled by the library and how to handle other types. ### 5\.3. Option with Multiple Arguments But now, what if we want our command to take multiple messages, as is done with the real [*git commit*](https://git-scm.com/docs/git-commit) command? No worries, **let’s make our field be an *array* or a *Collection***, and we’re pretty much done: ``` @Option(names = {"-m", "--message"}) private String[] messages; @Override public void run() { System.out.println("Committing files in the staging area, how wonderful?"); if (messages != null) { System.out.println("The commit message is"); for (String message : messages) { System.out.println(message); } } } ``` Now, we can use the *message* option multiple times: ``` commit -m "My commit is great" -m "My commit is beautiful" ``` However, we might also want to give the option only once and separate the different parameters by a regex delimiter. Hence, we can use the *split* parameter of the *@Option* annotation: ``` @Option(names = {"-m", "--message"}, split = ",") private String[] messages; ``` Now, we can pass *\-m “My commit is great”,”My commit is beautiful”* to achieve the same result as above. ### 5\.4. Required Option **Sometimes, we might have an option that is required. The *required* argument, which defaults to *false*, allows us to do that:** ``` @Option(names = {"-m", "--message"}, required = true) private String[] messages; ``` Now it’s impossible to call the *commit* command without specifying the *message* option. If we try to do that, *picocli* will print an error: ``` Missing required option '--message=<messages>' Usage: git commit -m=<messages> [-m=<messages>]... -m, --message=<messages> ``` ## 6\. Managing Positional Parameters ### 6\.1. Capture Positional Parameters Now, let’s focus on our *add* command because it’s not very powerful yet. We can only decide to add all files, but what if we wanted to add specific files? We could use another option to do that, but a better choice here would be to use positional parameters. Indeed, **positional parameters are meant to capture command arguments that occupy specific positions and are neither subcommands nor options.** In our example, this would enable us to do something like: ``` add file1 file2 ``` In order to capture positional parameters, **we’ll make use of the [*@Parameters*](https://picocli.info/#_positional_parameters) annotation**: ``` @Parameters private List<Path> files; @Override public void run() { if (allFiles) { System.out.println("Adding all files to the staging area"); } if (files != null) { files.forEach(path -> System.out.println("Adding " + path + " to the staging area")); } } ``` Now, our command from earlier would print: ``` Adding file1 to the staging area Adding file2 to the staging area ``` ### 6\.2. Capture a Subset of Positional Parameters It’s possible to be more fine-grained about which positional parameters to capture, thanks to the *index* parameter of the annotation. The index is zero-based. Thus, if we define: ``` @Parameters(index="2..*") ``` This would capture arguments that don’t match options or subcommands, from the third one to the end. The index can be either a range or a single number, representing a single position. ## 7\. A Word About Type Conversion As we’ve seen earlier in this tutorial, *picocli* handles some type conversion by itself. For example, it maps multiple values to *arrays* or *Collections*, but it can also map arguments to specific types like when we use the *Path* class for the *add* command. **As a matter of fact, *picocli* comes with [a bunch of pre-handled types](https://picocli.info/#_built_in_types). This means we can use those types directly without having to think about converting them ourselves.** However, we might need to map our command arguments to types other than those that are already handled. Fortunately for us, **this is possible thanks to the [*ITypeConverter*](https://picocli.info/#_custom_type_converters) interface and the *CommandLine\#registerConverter* method, which associates a type to a converter**. Let’s imagine we want to add the *config* subcommand to our *git* command, but we don’t want users to change a configuration element that doesn’t exist. So, we decide to map those elements to an enum: ``` public enum ConfigElement { USERNAME("user.name"), EMAIL("user.email"); private final String value; ConfigElement(String value) { this.value = value; } public String value() { return value; } public static ConfigElement from(String value) { return Arrays.stream(values()) .filter(element -> element.value.equals(value)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("The argument " + value + " doesn't match any ConfigElement")); } } ``` Plus, in our newly created *GitConfigCommand* class, let’s add two positional parameters: ``` @Parameters(index = "0") private ConfigElement element; @Parameters(index = "1") private String value; @Override public void run() { System.out.println("Setting " + element.value() + " to " + value); } ``` This way, we make sure that users won’t be able to change non-existent configuration elements. Finally, we have to register our converter. What’s beautiful is that, if using Java 8 or higher, we don’t even have to create a class implementing the *ITypeConverter* interface. **We can just pass a lambda or method reference to the *registerConverter()* method:** ``` CommandLine commandLine = new CommandLine(new GitCommand()); commandLine.registerConverter(ConfigElement.class, ConfigElement::from); commandLine.parseWithHandler(new RunLast(), args); ``` This happens in the *GitCommand* *main()* method. Note that we had to let go of the convenience *CommandLine.run()* method. When used with an unhandled configuration element, the command would show the help message plus a piece of information telling us that it wasn’t possible to convert the parameter to a *ConfigElement*: ``` Invalid value for positional parameter at index 0 (<element>): cannot convert 'user.phone' to ConfigElement (java.lang.IllegalArgumentException: The argument user.phone doesn't match any ConfigElement) Usage: git config <element> <value> <element> <value> ``` ## 8\. Integrating with Spring Boot Finally, let’s see how to Springify all that\! Indeed, we might be working within a Spring Boot environment and want to benefit from it in our command-line program. In order to do that, **we must create a *SpringBootApplication*implementing the *CommandLineRunner* interface**: ``` @SpringBootApplication public class Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void run(String... args) { } } ``` Plus, **let’s annotate all our commands and subcommands with the Spring *@Component* annotation** and autowire all that in our *Application*: ``` private GitCommand gitCommand; private GitAddCommand addCommand; private GitCommitCommand commitCommand; private GitConfigCommand configCommand; public Application(GitCommand gitCommand, GitAddCommand addCommand, GitCommitCommand commitCommand, GitConfigCommand configCommand) { this.gitCommand = gitCommand; this.addCommand = addCommand; this.commitCommand = commitCommand; this.configCommand = configCommand; } ``` Note that we had to autowire every subcommand. Unfortunately, this is because, for now, *picocli* is not yet able to retrieve subcommands from the Spring context when declared declaratively (with annotations). Thus, we’ll have to do that wiring ourselves, in a programmatic way: ``` @Override public void run(String... args) { CommandLine commandLine = new CommandLine(gitCommand); commandLine.addSubcommand("add", addCommand); commandLine.addSubcommand("commit", commitCommand); commandLine.addSubcommand("config", configCommand); commandLine.parseWithHandler(new CommandLine.RunLast(), args); } ``` And now, our command line program works like a charm with Spring components. Therefore, we could create some service classes and use them in our commands, and let Spring take care of the dependency injection. ## 9\. Conclusion In this article, we’ve seen some key features of the *picocli* library. We’ve learned how to create a new command and add some subcommands to it. We’ve seen many ways to deal with options and positional parameters. Plus, we’ve learned how to implement our own type converters to make our commands strongly typed. Finally, we’ve seen how to bring Spring Boot into our commands. Of course, there are many things more to discover about it. The library provides [complete documentation](https://picocli.info/). The code backing this article is available on GitHub. Once you're **logged in as a [Baeldung Pro Member](https://www.baeldung.com/members/)**, start learning and coding on the project.
Shard144 (laksa)
Root Hash17258965353624827544
Unparsed URLcom,baeldung!www,/java-picocli-create-command-line-program s443