Quarkus - Command Mode with Picocli

Picocli is an open source tool for creating rich command line applications.

Quarkus provides support for using Picocli. This guide contains examples of picocli extension usage.

This technology is considered experimental.

In experimental mode, early feedback is requested to mature the idea. There is no guarantee of stability nor long term presence in the platform until the solution matures. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

For a full list of possible extension statuses, check our FAQ entry.

If you are not familiar with the Quarkus Command Mode, consider reading the Command Mode reference guide first.

Configuration

Once you have your Quarkus project configured you can add the picocli extension to your project by running the following command in your project base directory.

  1. ./mvnw quarkus:add-extension -Dextensions="picocli"

This will add the following to your pom.xml:

  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-picocli</artifactId>
  4. </dependency>

Simple command line application

Simple PicocliApplication with only one Command can be created as follows:

  1. package com.acme.picocli;
  2. import picocli.CommandLine;
  3. import javax.enterprise.context.Dependent;
  4. import javax.inject.Inject;
  5. @CommandLine.Command (1)
  6. public class HelloCommand implements Runnable {
  7. @CommandLine.Option(names = {"-n", "--name"}, description = "Who will we greet?", defaultValue = "World")
  8. String name;
  9. private final GreetingService greetingService;
  10. public HelloCommand(GreetingService greetingService) { (2)
  11. this.greetingService = greetingService;
  12. }
  13. @Override
  14. public void run() {
  15. greetingService.sayHello(name);
  16. }
  17. }
  18. @Dependent
  19. class GreetingService {
  20. void sayHello(String name) {
  21. System.out.println("Hello " + name + "!");
  22. }
  23. }
1If there is only one class annotated with picocli.CommandLine.Command it will be used as entry point to Picocli CommandLine.
2All classes annotated with picocli.CommandLine.Command are registered as CDI beans.
Beans with @CommandLine.Command should not use proxied scopes (e.g. do not use @ApplicationScope) because Picocli will not be able set field values in such beans. This extension will register classes with @CommandLine.Command annotation using @Depended scope. If you need to use proxied scope, then annotate setter and not field, for example:
  1. @CommandLine.Command
  2. @ApplicationScoped
  3. public class EntryCommand {
  4. private String name;
  5. @CommandLine.Option(names = "-n")
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. }

Command line application with multiple Commands

When multiple classes have the picocli.CommandLine.Command annotation, then one of them needs to be also annotated with io.quarkus.picocli.runtime.annotations.TopCommand. This can be overwritten with the quarkus.picocli.top-command property.

  1. package com.acme.picocli;
  2. import io.quarkus.picocli.runtime.annotations.TopCommand;
  3. import picocli.CommandLine;
  4. @TopCommand
  5. @CommandLine.Command(mixinStandardHelpOptions = true, subcommands = {HelloCommand.class, GoodByeCommand.class})
  6. public class EntryCommand {
  7. }
  8. @CommandLine.Command(name = "hello", description = "Greet World!")
  9. class HelloCommand implements Runnable {
  10. @Override
  11. public void run() {
  12. System.out.println("Hello World!");
  13. }
  14. }
  15. @CommandLine.Command(name = "goodbye", description = "Say goodbye to World!")
  16. class GoodByeCommand implements Runnable {
  17. @Override
  18. public void run() {
  19. System.out.println("Goodbye World!");
  20. }
  21. }

Customizing Picocli CommandLine instance

You can customize CommandLine classes used by the picocli extension by producing your own bean instance:

  1. package com.acme.picocli;
  2. import io.quarkus.picocli.runtime.PicocliCommandLineFactory;
  3. import io.quarkus.picocli.runtime.annotations.TopCommand;
  4. import picocli.CommandLine;
  5. import javax.enterprise.context.ApplicationScoped;
  6. import javax.enterprise.inject.Produces;
  7. @TopCommand
  8. @CommandLine.Command
  9. public class EntryCommand implements Runnable {
  10. @CommandLine.Spec
  11. CommandLine.Model.CommandSpec spec;
  12. @Override
  13. public void run() {
  14. System.out.println("My name is: " + spec.name());
  15. }
  16. }
  17. @ApplicationScoped
  18. class CustomConfiguration {
  19. @Produces
  20. CommandLine customCommandLine(PicocliCommandLineFactory factory) { (1)
  21. return factory.create().setCommandName("CustomizedName");
  22. }
  23. }
1PicocliCommandLineFactory will create an instance of CommandLine with TopCommand and CommandLine.IFactory injected.

Different entry command for each profile

It is possible to create different entry command for each profile, using @IfBuildProfile:

  1. @ApplicationScoped
  2. public class Config {
  3. @Produces
  4. @TopCommand
  5. @IfBuildProfile("dev")
  6. public Object devCommand() {
  7. return DevCommand.class; (1)
  8. }
  9. @Produces
  10. @TopCommand
  11. @IfBuildProfile("prod")
  12. public Object prodCommand() {
  13. return new ProdCommand("Configured by me!");
  14. }
  15. }
1You can return instance of java.lang.Class here. In such case CommandLine will try to instantiate this class using CommandLine.IFactory.

Configure CDI Beans with parsed arguments

You can use Event<CommandLine.ParseResult> or just CommandLine.ParseResult to configure CDI beans based on arguments parsed by Picocli. This event will be generated in QuarkusApplication class created by this extension. If you are providing your own @QuarkusMain this event will not be raised. CommandLine.ParseResult is created from default CommandLine bean.

  1. @CommandLine.Command
  2. public class EntryCommand implements Runnable {
  3. @CommandLine.Option(names = "-c", description = "JDBC connection string")
  4. String connectionString;
  5. @Inject
  6. DataSource dataSource;
  7. @Override
  8. public void run() {
  9. try (Connection c = dataSource.getConnection()) {
  10. // Do something
  11. } catch (SQLException throwables) {
  12. // Handle error
  13. }
  14. }
  15. }
  16. @ApplicationScoped
  17. class DatasourceConfiguration {
  18. @Produces
  19. @ApplicationScoped (1)
  20. DataSource dataSource(CommandLine.ParseResult parseResult) {
  21. PGSimpleDataSource ds = new PGSimpleDataSource();
  22. ds.setURL(parseResult.matchedOption("c").getValue().toString());
  23. return ds;
  24. }
  25. }
1@ApplicationScoped used for lazy initialization

Providing own QuarkusMain

You can also provide your own application entry point annotated with QuarkusMain (as described in Command Mode reference guide).

  1. package com.acme.picocli;
  2. import io.quarkus.runtime.QuarkusApplication;
  3. import io.quarkus.runtime.annotations.QuarkusMain;
  4. import picocli.CommandLine;
  5. import javax.inject.Inject;
  6. @QuarkusMain
  7. @CommandLine.Command(name = "demo", mixinStandardHelpOptions = true)
  8. public class ExampleApp implements Runnable, QuarkusApplication {
  9. @Inject
  10. CommandLine.IFactory factory; (1)
  11. @Override
  12. public void run() {
  13. // business logic
  14. }
  15. @Override
  16. public int run(String... args) throws Exception {
  17. return new CommandLine(this, factory).execute(args);
  18. }
  19. }
1Quarkus-compatible CommandLine.IFactory bean created by picocli extension.

Native mode support

This extension uses the Quarkus standard build steps mechanism to support GraalVM Native images. In the exceptional case that incompatible changes in a future picocli release cause any issue, the following configuration can be used to fall back to the annotation processor from the picocli project as a temporary workaround:

  1. <dependency>
  2. <groupId>info.picocli</groupId>
  3. <artifactId>picocli-codegen</artifactId>
  4. </dependency>

For Gradle, you need to add the following in dependencies section of the build.gradle file:

  1. annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
  2. annotationProcessor 'info.picocli:picocli-codegen'

Configuration Reference