Back to Guides

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.

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.

CLI
quarkus extension add 'picocli'
Maven
./mvnw quarkus:add-extension -Dextensions='picocli'
Gradle
./gradlew addExtension --extensions='picocli'

This will add the following to your pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-picocli</artifactId>
</dependency>

Simple command line application

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

package com.acme.picocli;

import picocli.CommandLine;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;

@CommandLine.Command (1)
public class HelloCommand implements Runnable {

    @CommandLine.Option(names = {"-n", "--name"}, description = "Who will we greet?", defaultValue = "World")
    String name;

    private final GreetingService greetingService;

    public HelloCommand(GreetingService greetingService) { (2)
        this.greetingService = greetingService;
    }

    @Override
    public void run() {
        greetingService.sayHello(name);
    }
}

@Dependent
class GreetingService {
    void sayHello(String name) {
        System.out.println("Hello " + name + "!");
    }
}
1 If there is only one class annotated with picocli.CommandLine.Command it will be used as entry point to Picocli CommandLine.
2 All 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 to 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:
@CommandLine.Command
@ApplicationScoped
public class EntryCommand {
    private String name;

    @CommandLine.Option(names = "-n")
    public void setName(String name) {
        this.name = name;
    }

}

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.

package com.acme.picocli;

import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;

@TopCommand
@CommandLine.Command(mixinStandardHelpOptions = true, subcommands = {HelloCommand.class, GoodByeCommand.class})
public class EntryCommand {
}

@CommandLine.Command(name = "hello", description = "Greet World!")
class HelloCommand implements Runnable {

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}

@CommandLine.Command(name = "goodbye", description = "Say goodbye to World!")
class GoodByeCommand implements Runnable {

    @Override
    public void run() {
        System.out.println("Goodbye World!");
    }
}

Customizing Picocli CommandLine instance

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

package com.acme.picocli;

import io.quarkus.picocli.runtime.PicocliCommandLineFactory;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

@TopCommand
@CommandLine.Command
public class EntryCommand implements Runnable {
    @CommandLine.Spec
    CommandLine.Model.CommandSpec spec;

    @Override
    public void run() {
        System.out.println("My name is: " + spec.name());
    }
}

@ApplicationScoped
class CustomConfiguration {

    @Produces
    CommandLine customCommandLine(PicocliCommandLineFactory factory) { (1)
        return factory.create().setCommandName("CustomizedName");
    }
}
1 PicocliCommandLineFactory 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:

@ApplicationScoped
public class Config {

    @Produces
    @TopCommand
    @IfBuildProfile("dev")
    public Object devCommand() {
        return DevCommand.class; (1)
    }

    @Produces
    @TopCommand
    @IfBuildProfile("prod")
    public Object prodCommand() {
        return new ProdCommand("Configured by me!");
    }

}
1 You 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.

@CommandLine.Command
public class EntryCommand implements Runnable {

    @CommandLine.Option(names = "-c", description = "JDBC connection string")
    String connectionString;

    @Inject
    DataSource dataSource;

    @Override
    public void run() {
        try (Connection c = dataSource.getConnection()) {
            // Do something
        } catch (SQLException throwables) {
            // Handle error
        }
    }
}

@ApplicationScoped
class DatasourceConfiguration {

    @Produces
    @ApplicationScoped (1)
    DataSource dataSource(CommandLine.ParseResult parseResult) {
        PGSimpleDataSource ds = new PGSimpleDataSource();
        ds.setURL(parseResult.matchedOption("c").getValue().toString());
        return ds;
    }
}
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).

package com.acme.picocli;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
import picocli.CommandLine;

import javax.inject.Inject;

@QuarkusMain
@CommandLine.Command(name = "demo", mixinStandardHelpOptions = true)
public class ExampleApp implements Runnable, QuarkusApplication {
    @Inject
    CommandLine.IFactory factory; (1)

    @Override
    public void run() {
        // business logic
    }

    @Override
    public int run(String... args) throws Exception {
        return new CommandLine(this, factory).execute(args);
    }
}
1 Quarkus-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:

<dependency>
  <groupId>info.picocli</groupId>
  <artifactId>picocli-codegen</artifactId>
</dependency>

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

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

Development Mode

In the development mode, i.e. when running mvn quarkus:dev, the application is executed and restarted every time the Space bar key is pressed. You can also pass arguments to your command line app via the quarkus.args system property, e.g. mvn quarkus:dev -Dquarkus.args='--help' and mvn quarkus:dev -Dquarkus.args='-c -w --val 1'.

Kubernetes support

Once you have your command line application, you can also generate the resources necessary to install and use this application in Kubernetes by adding the kubernetes extension. To install the kubernetes extension, run the following command in your project base directory.

CLI
quarkus extension add 'kubernetes'
Maven
./mvnw quarkus:add-extension -Dextensions='kubernetes'
Gradle
./gradlew addExtension --extensions='kubernetes'

This will add the following to your pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-kubernetes</artifactId>
</dependency>

And, next, build the application with:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

The Kubernetes extension will detect the presence of the Picocli extension and hence generate a Job resource instead of a Deployment resource in the target/kubernetes/ directory.

If you don’t want to generate a Job resource, you can specify the resource you want to generate using the property quarkus.kubernetes.deployment-kind. For example, if you want to generate a Deployment resource, use quarkus.kubernetes.deployment-kind=Deployment.

Moreover, you can provide the arguments that will be used by the Kubernetes job via the property quarkus.kubernetes.arguments. For example, after adding the property quarkus.kubernetes.arguments=A,B and building your project, the following Job resource will be generated:

apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/name: app
    app.kubernetes.io/version: 0.1-SNAPSHOT
  name: app
spec:
  completionMode: NonIndexed
  selector:
    matchLabels:
      app.kubernetes.io/name: app
      app.kubernetes.io/version: 0.1-SNAPSHOT
  suspend: false
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app
        app.kubernetes.io/version: 0.1-SNAPSHOT
    spec:
      containers:
        - args:
            - A
            - B
          env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: docker.io/user/app:0.1-SNAPSHOT
          imagePullPolicy: Always
          name: app
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
      restartPolicy: OnFailure
      terminationGracePeriodSeconds: 10

Finally, the Kubernetes job will be launched every time it is installed in Kubernetes. You can know more about how to run Kubernetes jobs in this document.

Configuration Reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Set this to false to use the picocli-codegen annotation processor instead of build steps. CAUTION: this will have serious build-time performance impact since this is run on every restart in dev mode, use with care! This property is intended to be used only in cases where an incompatible change in the picocli library causes problems in the build steps used to support GraalVM Native images. In such cases this property allows users to make the trade-off between fast build cycles with the older version of picocli, and temporarily accept slower build cycles with the latest version of picocli until the updated extension is available.

Environment variable: QUARKUS_PICOCLI_NATIVE_IMAGE_PROCESSING_ENABLE

boolean

true

Name of bean annotated with io.quarkus.picocli.runtime.annotations.TopCommand or FQCN of class which will be used as entry point for Picocli CommandLine instance. This class needs to be annotated with picocli.CommandLine.Command.

Environment variable: QUARKUS_PICOCLI_TOP_COMMAND

string