This is an automated email from the ASF dual-hosted git repository.

zixuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new 7644a027502 [improve][cli] PIP-343: Use picocli instead of jcommander 
in bin/pulsar (#22288)
7644a027502 is described below

commit 7644a02750239af30ab707702d1a3f97596b08fd
Author: Zixuan Liu <[email protected]>
AuthorDate: Fri Mar 22 20:50:25 2024 +0800

    [improve][cli] PIP-343: Use picocli instead of jcommander in bin/pulsar 
(#22288)
    
    Signed-off-by: Zixuan Liu <[email protected]>
---
 pulsar-broker/pom.xml                              |   4 +-
 .../org/apache/pulsar/PulsarBrokerStarter.java     | 135 +++++++------
 .../apache/pulsar/PulsarClusterMetadataSetup.java  |  88 ++++-----
 .../pulsar/PulsarClusterMetadataTeardown.java      |  32 ++--
 .../apache/pulsar/PulsarInitialNamespaceSetup.java |  33 ++--
 .../java/org/apache/pulsar/PulsarStandalone.java   |  39 ++--
 .../org/apache/pulsar/PulsarStandaloneStarter.java |  18 +-
 .../PulsarTransactionCoordinatorMetadataSetup.java |  32 ++--
 .../org/apache/pulsar/PulsarVersionStarter.java    |  22 ++-
 .../org/apache/pulsar/broker/tools/BrokerTool.java |  34 ++--
 .../pulsar/broker/tools/GenerateDocsCommand.java   |  61 ++----
 .../pulsar/broker/tools/LoadReportCommand.java     |  78 +++-----
 .../apache/pulsar/compaction/CompactorTool.java    |  27 +--
 .../pulsar/utils/auth/tokens/TokensCliUtils.java   | 211 ++++++++++-----------
 .../org/apache/pulsar/PulsarBrokerStarterTest.java | 109 +++++------
 .../pulsar/PulsarClusterMetadataSetupTest.java     |  12 +-
 .../pulsar/PulsarClusterMetadataTeardownTest.java  |   6 +-
 .../pulsar/PulsarInitialNamespaceSetupTest.java    |   6 +-
 ...sarTransactionCoordinatorMetadataSetupTest.java |   6 +-
 .../apache/pulsar/PulsarVersionStarterTest.java    |   6 +-
 .../apache/pulsar/broker/tools/BrokerToolTest.java |   8 +-
 .../pulsar/compaction/CompactorToolTest.java       |   6 +-
 .../utils/auth/tokens/TokensCliUtilsTest.java      |   8 +-
 pulsar-docs-tools/pom.xml                          |   4 +-
 .../docs/tools/BaseGenerateDocumentation.java      |  50 +++--
 .../apache/pulsar/docs/tools/CmdGenerateDocs.java  | 171 +++++++++--------
 .../pulsar/docs/tools/CmdGenerateDocsTest.java     |  96 ++++------
 .../functions/worker/FunctionWorkerStarter.java    |  25 +--
 .../worker/FunctionWorkerStarterTest.java          |   6 +-
 pulsar-io/docs/pom.xml                             |   4 +-
 pulsar-proxy/pom.xml                               |   4 +-
 .../pulsar/proxy/server/ProxyServiceStarter.java   |  34 ++--
 .../websocket/service/WebSocketServiceStarter.java |  25 +--
 .../service/WebSocketServiceStarterTest.java       |   6 +-
 34 files changed, 691 insertions(+), 715 deletions(-)

diff --git a/pulsar-broker/pom.xml b/pulsar-broker/pom.xml
index db839467ed2..8264459c6d9 100644
--- a/pulsar-broker/pom.xml
+++ b/pulsar-broker/pom.xml
@@ -322,8 +322,8 @@
     </dependency>
 
     <dependency>
-      <groupId>com.beust</groupId>
-      <artifactId>jcommander</artifactId>
+      <groupId>info.picocli</groupId>
+      <artifactId>picocli</artifactId>
     </dependency>
 
     <dependency>
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java
index 1b24c806e62..bd5b5399b00 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java
@@ -23,9 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
 import static 
org.apache.pulsar.common.configuration.PulsarConfigurationLoader.create;
 import static 
org.apache.pulsar.common.configuration.PulsarConfigurationLoader.isComplete;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
 import java.io.File;
 import java.io.FileInputStream;
@@ -34,10 +31,10 @@ import java.net.MalformedURLException;
 import java.nio.file.Path;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
 import org.apache.bookkeeper.common.component.ComponentStarter;
 import org.apache.bookkeeper.common.component.LifecycleComponent;
@@ -63,7 +60,13 @@ import org.apache.pulsar.functions.worker.WorkerService;
 import org.apache.pulsar.functions.worker.service.WorkerServiceLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.ArgGroup;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
+@Command(description = "broker", showDefaultValues = true, scope = 
ScopeType.INHERIT)
 public class PulsarBrokerStarter {
 
     private static ServiceConfiguration loadConfig(String configFile) throws 
Exception {
@@ -76,31 +79,30 @@ public class PulsarBrokerStarter {
     }
 
     @VisibleForTesting
-    @Parameters(commandDescription = "Options")
     private static class StarterArguments {
-        @Parameter(names = {"-c", "--broker-conf"}, description = 
"Configuration file for Broker")
+        @Option(names = {"-c", "--broker-conf"}, description = "Configuration 
file for Broker")
         private String brokerConfigFile = "conf/broker.conf";
 
-        @Parameter(names = {"-rb", "--run-bookie"}, description = "Run Bookie 
together with Broker")
+        @Option(names = {"-rb", "--run-bookie"}, description = "Run Bookie 
together with Broker")
         private boolean runBookie = false;
 
-        @Parameter(names = {"-ra", "--run-bookie-autorecovery"},
+        @Option(names = {"-ra", "--run-bookie-autorecovery"},
                 description = "Run Bookie Autorecovery together with broker")
         private boolean runBookieAutoRecovery = false;
 
-        @Parameter(names = {"-bc", "--bookie-conf"}, description = 
"Configuration file for Bookie")
+        @Option(names = {"-bc", "--bookie-conf"}, description = "Configuration 
file for Bookie")
         private String bookieConfigFile = "conf/bookkeeper.conf";
 
-        @Parameter(names = {"-rfw", "--run-functions-worker"}, description = 
"Run functions worker with Broker")
+        @Option(names = {"-rfw", "--run-functions-worker"}, description = "Run 
functions worker with Broker")
         private boolean runFunctionsWorker = false;
 
-        @Parameter(names = {"-fwc", "--functions-worker-conf"}, description = 
"Configuration file for Functions Worker")
+        @Option(names = {"-fwc", "--functions-worker-conf"}, description = 
"Configuration file for Functions Worker")
         private String fnWorkerConfigFile = "conf/functions_worker.yml";
 
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
@@ -125,43 +127,46 @@ public class PulsarBrokerStarter {
         return bookieConf;
     }
 
-    private static boolean argsContains(String[] args, String arg) {
-        return Arrays.asList(args).contains(arg);
-    }
-
-    private static class BrokerStarter {
-        private final ServiceConfiguration brokerConfig;
-        private final PulsarService pulsarService;
-        private final LifecycleComponent bookieServer;
+    protected static class BrokerStarter implements Callable<Integer> {
+        private ServiceConfiguration brokerConfig;
+        private PulsarService pulsarService;
+        private LifecycleComponent bookieServer;
         private volatile CompletableFuture<Void> bookieStartFuture;
-        private final AutoRecoveryMain autoRecoveryMain;
-        private final StatsProvider bookieStatsProvider;
-        private final ServerConfiguration bookieConfig;
-        private final WorkerService functionsWorkerService;
-        private final WorkerConfig workerConfig;
-
-        BrokerStarter(String[] args) throws Exception {
-            StarterArguments starterArguments = new StarterArguments();
-            JCommander jcommander = new JCommander(starterArguments);
-            jcommander.setProgramName("PulsarBrokerStarter");
-
-            // parse args by JCommander
-            jcommander.parse(args);
+        private AutoRecoveryMain autoRecoveryMain;
+        private StatsProvider bookieStatsProvider;
+        private ServerConfiguration bookieConfig;
+        private WorkerService functionsWorkerService;
+        private WorkerConfig workerConfig;
+
+        private CommandLine commander;
+
+        @ArgGroup(exclusive = false)
+        private final StarterArguments starterArguments = new 
StarterArguments();
+
+        BrokerStarter() {
+            commander = new CommandLine(this);
+        }
+
+        public int start(String[] args) {
+            return commander.execute(args);
+        }
+
+        public Integer call() throws Exception {
             if (starterArguments.help) {
-                jcommander.usage();
-                System.exit(0);
+                commander.usage(commander.getOut());
+                return 0;
             }
 
             if (starterArguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("broker", starterArguments);
+                cmd.addCommand("broker", commander);
                 cmd.run(null);
-                System.exit(0);
+                return 0;
             }
 
             // init broker config
             if (isBlank(starterArguments.brokerConfigFile)) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 throw new IllegalArgumentException("Need to specify a 
configuration file for broker");
             } else {
                 final String filepath = 
Path.of(starterArguments.brokerConfigFile)
@@ -209,20 +214,16 @@ public class PulsarBrokerStarter {
                                               });
 
             // if no argument to run bookie in cmd line, read from pulsar 
config
-            if (!argsContains(args, "-rb") && !argsContains(args, 
"--run-bookie")) {
-                checkState(!starterArguments.runBookie,
-                        "runBookie should be false if has no argument 
specified");
+            if (!starterArguments.runBookie) {
                 starterArguments.runBookie = 
brokerConfig.isEnableRunBookieTogether();
             }
-            if (!argsContains(args, "-ra") && !argsContains(args, 
"--run-bookie-autorecovery")) {
-                checkState(!starterArguments.runBookieAutoRecovery,
-                        "runBookieAutoRecovery should be false if has no 
argument specified");
+            if (!starterArguments.runBookieAutoRecovery) {
                 starterArguments.runBookieAutoRecovery = 
brokerConfig.isEnableRunBookieAutoRecoveryTogether();
             }
 
             if ((starterArguments.runBookie || 
starterArguments.runBookieAutoRecovery)
-                && isBlank(starterArguments.bookieConfigFile)) {
-                jcommander.usage();
+                    && isBlank(starterArguments.bookieConfigFile)) {
+                commander.usage(commander.getOut());
                 throw new IllegalArgumentException("No configuration file for 
Bookie");
             }
 
@@ -257,9 +258,7 @@ public class PulsarBrokerStarter {
             } else {
                 autoRecoveryMain = null;
             }
-        }
 
-        public void start() throws Exception {
             if (bookieStatsProvider != null) {
                 bookieStatsProvider.start(bookieConfig);
                 log.info("started bookieStatsProvider.");
@@ -275,15 +274,17 @@ public class PulsarBrokerStarter {
 
             pulsarService.start();
             log.info("PulsarService started.");
+            return 0;
         }
 
         public void join() throws InterruptedException {
-            pulsarService.waitUntilClosed();
-
-            try {
-                pulsarService.close();
-            } catch (PulsarServerException e) {
-                throw new RuntimeException();
+            if (pulsarService != null) {
+                pulsarService.waitUntilClosed();
+                try {
+                    pulsarService.close();
+                } catch (PulsarServerException e) {
+                    throw new RuntimeException();
+                }
             }
 
             if (bookieStartFuture != null) {
@@ -301,8 +302,10 @@ public class PulsarBrokerStarter {
                 log.info("Shut down functions worker service successfully.");
             }
 
-            pulsarService.close();
-            log.info("Shut down broker service successfully.");
+            if (pulsarService != null) {
+                pulsarService.close();
+                log.info("Shut down broker service successfully.");
+            }
 
             if (bookieStatsProvider != null) {
                 bookieStatsProvider.stop();
@@ -317,6 +320,11 @@ public class PulsarBrokerStarter {
                 log.info("Shut down autoRecoveryMain successfully.");
             }
         }
+
+        @VisibleForTesting
+        CommandLine getCommander() {
+            return commander;
+        }
     }
 
 
@@ -330,7 +338,7 @@ public class PulsarBrokerStarter {
             exception.printStackTrace(System.out);
         });
 
-        BrokerStarter starter = new BrokerStarter(args);
+        BrokerStarter starter = new BrokerStarter();
         Runtime.getRuntime().addShutdownHook(
             new Thread(() -> {
                 try {
@@ -344,16 +352,21 @@ public class PulsarBrokerStarter {
         );
 
         PulsarByteBufAllocator.registerOOMListener(oomException -> {
-            if (starter.brokerConfig.isSkipBrokerShutdownOnOOM()) {
+            if (starter.brokerConfig != null && 
starter.brokerConfig.isSkipBrokerShutdownOnOOM()) {
                 log.error("-- Received OOM exception: {}", 
oomException.getMessage(), oomException);
             } else {
                 log.error("-- Shutting down - Received OOM exception: {}", 
oomException.getMessage(), oomException);
-                starter.pulsarService.shutdownNow();
+                if (starter.pulsarService != null) {
+                    starter.pulsarService.shutdownNow();
+                }
             }
         });
 
         try {
-            starter.start();
+            int start = starter.start(args);
+            if (start != 0) {
+                System.exit(start);
+            }
         } catch (Throwable t) {
             log.error("Failed to start pulsar service.", t);
             ShutdownUtil.triggerImmediateForcefulShutdown();
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java
index 96ebadb1ff4..854a5179161 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java
@@ -19,8 +19,6 @@
 package org.apache.pulsar;
 
 import static org.apache.pulsar.common.policies.data.PoliciesUtil.getBundles;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Optional;
@@ -58,6 +56,10 @@ import 
org.apache.pulsar.metadata.impl.MetadataStoreFactoryImpl;
 import org.apache.pulsar.metadata.impl.ZKMetadataStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Setup the metadata for a new Pulsar cluster.
@@ -66,75 +68,76 @@ public class PulsarClusterMetadataSetup {
 
     private static final int DEFAULT_BUNDLE_NUMBER = 16;
 
+    @Command(name = "initialize-cluster-metadata", showDefaultValues = true, 
scope = ScopeType.INHERIT)
     private static class Arguments {
-        @Parameter(names = { "-c", "--cluster" }, description = "Cluster 
name", required = true)
+        @Option(names = {"-c", "--cluster"}, description = "Cluster name", 
required = true)
         private String cluster;
 
-        @Parameter(names = {"-bn",
+        @Option(names = {"-bn",
                 "--default-namespace-bundle-number"},
                 description = "The bundle numbers for the default 
namespaces(public/default), default is 16",
                 required = false)
         private int numberOfDefaultNamespaceBundles;
 
-        @Parameter(names = { "-uw",
-                "--web-service-url" }, description = "Web-service URL for new 
cluster", required = true)
+        @Option(names = {"-uw",
+                "--web-service-url"}, description = "Web-service URL for new 
cluster", required = true)
         private String clusterWebServiceUrl;
 
-        @Parameter(names = {"-tw",
+        @Option(names = {"-tw",
                 "--web-service-url-tls"},
                 description = "Web-service URL for new cluster with TLS 
encryption", required = false)
         private String clusterWebServiceUrlTls;
 
-        @Parameter(names = { "-ub",
-                "--broker-service-url" }, description = "Broker-service URL 
for new cluster", required = false)
+        @Option(names = {"-ub",
+                "--broker-service-url"}, description = "Broker-service URL for 
new cluster", required = false)
         private String clusterBrokerServiceUrl;
 
-        @Parameter(names = {"-tb",
+        @Option(names = {"-tb",
                 "--broker-service-url-tls"},
                 description = "Broker-service URL for new cluster with TLS 
encryption", required = false)
         private String clusterBrokerServiceUrlTls;
 
-        @Parameter(names = { "-zk",
-                "--zookeeper" }, description = "Local ZooKeeper quorum 
connection string",
+        @Option(names = {"-zk",
+                "--zookeeper"}, description = "Local ZooKeeper quorum 
connection string",
                 required = false,
                 hidden = true
-                )
+        )
         private String zookeeper;
 
-        @Parameter(names = { "-md",
-                "--metadata-store" }, description = "Metadata Store service 
url. eg: zk:my-zk:2181", required = false)
+        @Option(names = {"-md",
+                "--metadata-store"}, description = "Metadata Store service 
url. eg: zk:my-zk:2181", required = false)
         private String metadataStoreUrl;
 
-        @Parameter(names = {
-            "--zookeeper-session-timeout-ms"
+        @Option(names = {
+                "--zookeeper-session-timeout-ms"
         }, description = "Local zookeeper session timeout ms")
         private int zkSessionTimeoutMillis = 30000;
 
-        @Parameter(names = {"-gzk",
+        @Option(names = {"-gzk",
                 "--global-zookeeper"},
                 description = "Global ZooKeeper quorum connection string", 
required = false, hidden = true)
         private String globalZookeeper;
 
-        @Parameter(names = {"-cs",
+        @Option(names = {"-cs",
                 "--configuration-store"}, description = "Configuration Store 
connection string", hidden = true)
         private String configurationStore;
 
-        @Parameter(names = {"-cms",
+        @Option(names = {"-cms",
                 "--configuration-metadata-store"}, description = 
"Configuration Metadata Store connection string",
                 hidden = false)
         private String configurationMetadataStore;
 
-        @Parameter(names = {
-            "--initial-num-stream-storage-containers"
+        @Option(names = {
+                "--initial-num-stream-storage-containers"
         }, description = "Num storage containers of BookKeeper stream storage")
         private int numStreamStorageContainers = 16;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--initial-num-transaction-coordinators"
         }, description = "Num transaction coordinators will assigned in 
cluster")
         private int numTransactionCoordinators = 16;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--existing-bk-metadata-service-uri"},
                 description = "The metadata service URI of the existing 
BookKeeper cluster that you want to use")
         private String existingBkMetadataServiceUri;
@@ -142,26 +145,26 @@ public class PulsarClusterMetadataSetup {
         // Hide and marked as deprecated this flag because we use the new name 
'--existing-bk-metadata-service-uri' to
         // pass the service url. For compatibility of the command, we should 
keep both to avoid the exceptions.
         @Deprecated
-        @Parameter(names = {
-            "--bookkeeper-metadata-service-uri"},
-            description = "The metadata service URI of the existing BookKeeper 
cluster that you want to use",
-            hidden = true)
+        @Option(names = {
+                "--bookkeeper-metadata-service-uri"},
+                description = "The metadata service URI of the existing 
BookKeeper cluster that you want to use",
+                hidden = true)
         private String bookieMetadataServiceUri;
 
-        @Parameter(names = { "-pp",
-                "--proxy-protocol" },
+        @Option(names = {"-pp",
+                "--proxy-protocol"},
                 description = "Proxy protocol to select type of routing at 
proxy. Possible Values: [SNI]",
                 required = false)
         private ProxyProtocol clusterProxyProtocol;
 
-        @Parameter(names = { "-pu",
-                "--proxy-url" }, description = "Proxy-server URL to which to 
connect.", required = false)
+        @Option(names = {"-pu",
+                "--proxy-url"}, description = "Proxy-server URL to which to 
connect.", required = false)
         private String clusterProxyUrl;
 
-        @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
@@ -197,28 +200,27 @@ public class PulsarClusterMetadataSetup {
         System.setProperty("bookkeeper.metadata.client.drivers", 
PulsarMetadataClientDriver.class.getName());
 
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
             if (arguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("initialize-cluster-metadata", arguments);
+                cmd.addCommand("initialize-cluster-metadata", commander);
                 cmd.run(null);
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.getErr().println(e);
             throw e;
         }
 
         if (arguments.metadataStoreUrl == null && arguments.zookeeper == null) 
{
             System.err.println("Metadata store address argument is required 
(--metadata-store)");
-            jcommander.usage();
+            commander.usage(commander.getOut());
             System.exit(1);
         }
 
@@ -226,7 +228,7 @@ public class PulsarClusterMetadataSetup {
                 && arguments.globalZookeeper == null) {
             System.err.println(
                     "Configuration metadata store address argument is required 
(--configuration-metadata-store)");
-            jcommander.usage();
+            commander.usage(commander.getOut());
             System.exit(1);
         }
 
@@ -234,7 +236,7 @@ public class PulsarClusterMetadataSetup {
                 || arguments.globalZookeeper != null)) {
             System.err.println("Configuration metadata store argument 
(--configuration-metadata-store) "
                     + "supersedes the deprecated (--global-zookeeper and 
--configuration-store) argument");
-            jcommander.usage();
+            commander.usage(commander.getOut());
             System.exit(1);
         }
 
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java
index 01a9eedcca3..a2984a352b9 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java
@@ -18,8 +18,6 @@
  */
 package org.apache.pulsar;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
@@ -41,35 +39,40 @@ import org.apache.pulsar.metadata.api.MetadataStoreFactory;
 import org.apache.pulsar.metadata.api.extended.MetadataStoreExtended;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Teardown the metadata for a existed Pulsar cluster.
  */
 public class PulsarClusterMetadataTeardown {
 
+    @Command(name = "delete-cluster-metadata", showDefaultValues = true, scope 
= ScopeType.INHERIT)
     private static class Arguments {
-        @Parameter(names = { "-zk",
+        @Option(names = { "-zk",
                 "--zookeeper"}, description = "Local ZooKeeper quorum 
connection string", required = true)
         private String zookeeper;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--zookeeper-session-timeout-ms"
         }, description = "Local zookeeper session timeout ms")
         private int zkSessionTimeoutMillis = 30000;
 
-        @Parameter(names = { "-c", "-cluster", "--cluster" }, description = 
"Cluster name")
+        @Option(names = { "-c", "-cluster", "--cluster" }, description = 
"Cluster name")
         private String cluster;
 
-        @Parameter(names = { "-cs", "--configuration-store" }, description = 
"Configuration Store connection string")
+        @Option(names = { "-cs", "--configuration-store" }, description = 
"Configuration Store connection string")
         private String configurationStore;
 
-        @Parameter(names = { "--bookkeeper-metadata-service-uri" }, 
description = "Metadata service uri of BookKeeper")
+        @Option(names = { "--bookkeeper-metadata-service-uri" }, description = 
"Metadata service uri of BookKeeper")
         private String bkMetadataServiceUri;
 
-        @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+        @Option(names = { "-h", "--help" }, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
@@ -78,22 +81,21 @@ public class PulsarClusterMetadataTeardown {
 
     public static void main(String[] args) throws Exception {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
             if (arguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("delete-cluster-metadata", arguments);
+                cmd.addCommand("delete-cluster-metadata", commander);
                 cmd.run(null);
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.getErr().println(e);
             throw e;
         }
 
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java
index 22b38e59676..891aa1aa421 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java
@@ -18,67 +18,70 @@
  */
 package org.apache.pulsar;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import java.util.List;
 import org.apache.pulsar.broker.resources.PulsarResources;
 import org.apache.pulsar.common.naming.NamespaceName;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
 import org.apache.pulsar.metadata.api.MetadataStore;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Setup the initial namespace of the cluster without startup the Pulsar 
broker.
  */
 public class PulsarInitialNamespaceSetup {
 
+    @Command(name = "initialize-namespace", showDefaultValues = true, scope = 
ScopeType.INHERIT)
     private static class Arguments {
 
-        @Parameter(names = { "-c", "--cluster" }, description = "Cluster 
name", required = true)
+        @Option(names = { "-c", "--cluster" }, description = "Cluster name", 
required = true)
         private String cluster;
 
-        @Parameter(names = { "-cs",
+        @Option(names = { "-cs",
                 "--configuration-store" }, description = "Configuration Store 
connection string", required = true)
         private String configurationStore;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--zookeeper-session-timeout-ms"
         }, description = "Local zookeeper session timeout ms")
         private int zkSessionTimeoutMillis = 30000;
 
-        @Parameter(description = "tenant/namespace", required = true)
+        @Parameters(description = "tenant/namespace", arity = "1")
         private List<String> namespaces;
 
-        @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+        @Option(names = { "-h", "--help" }, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
     public static int doMain(String[] args) throws Exception {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return 0;
             }
             if (arguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("initialize-namespace", arguments);
+                cmd.addCommand("initialize-namespace", commander);
                 cmd.run(null);
                 return 0;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.getErr().println(e);
             return 1;
         }
 
         if (arguments.configurationStore == null) {
             System.err.println("Configuration store address argument is 
required (--configuration-store)");
-            jcommander.usage();
+            commander.usage(commander.getOut());
             return 1;
         }
 
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java
index ba136e7c910..b785448cdac 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java
@@ -20,7 +20,6 @@ package org.apache.pulsar;
 
 import static org.apache.pulsar.common.naming.NamespaceName.SYSTEM_NAMESPACE;
 import static 
org.apache.pulsar.common.naming.SystemTopicNames.TRANSACTION_COORDINATOR_ASSIGN;
-import com.beust.jcommander.Parameter;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Sets;
 import io.netty.util.internal.PlatformDependent;
@@ -52,8 +51,12 @@ import org.apache.pulsar.metadata.bookkeeper.BKCluster;
 import org.apache.pulsar.metadata.impl.ZKMetadataStore;
 import 
org.apache.pulsar.packages.management.storage.filesystem.FileSystemPackagesStorageProvider;
 import org.apache.pulsar.zookeeper.LocalBookkeeperEnsemble;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 @Slf4j
+@Command(name = "standalone", showDefaultValues = true, scope = 
ScopeType.INHERIT)
 public class PulsarStandalone implements AutoCloseable {
 
     private static final String PULSAR_STANDALONE_USE_ZOOKEEPER = 
"PULSAR_STANDALONE_USE_ZOOKEEPER";
@@ -211,60 +214,60 @@ public class PulsarStandalone implements AutoCloseable {
         return help;
     }
 
-    @Parameter(names = { "-c", "--config" }, description = "Configuration file 
path")
+    @Option(names = { "-c", "--config" }, description = "Configuration file 
path")
     private String configFile;
 
-    @Parameter(names = { "--wipe-data" }, description = "Clean up previous 
ZK/BK data")
+    @Option(names = { "--wipe-data" }, description = "Clean up previous ZK/BK 
data")
     private boolean wipeData = false;
 
-    @Parameter(names = { "--num-bookies" }, description = "Number of local 
Bookies")
+    @Option(names = { "--num-bookies" }, description = "Number of local 
Bookies")
     private int numOfBk = 1;
 
-    @Parameter(names = { "--metadata-dir" },
+    @Option(names = { "--metadata-dir" },
             description = "Directory for storing metadata")
     private String metadataDir = "data/metadata";
 
-    @Parameter(names = { "--metadata-url" },
+    @Option(names = { "--metadata-url" },
             description = "Metadata store url")
     private String metadataStoreUrl = "";
 
-    @Parameter(names = {"--zookeeper-port"}, description = "Local zookeeper's 
port",
+    @Option(names = {"--zookeeper-port"}, description = "Local zookeeper's 
port",
             hidden = true)
     private int zkPort = 2181;
 
-    @Parameter(names = { "--bookkeeper-port" }, description = "Local bookies 
base port")
+    @Option(names = { "--bookkeeper-port" }, description = "Local bookies base 
port")
     private int bkPort = 3181;
 
-    @Parameter(names = { "--zookeeper-dir" },
+    @Option(names = { "--zookeeper-dir" },
             description = "Local zooKeeper's data directory",
             hidden = true)
     private String zkDir = "data/standalone/zookeeper";
 
-    @Parameter(names = { "--bookkeeper-dir" }, description = "Local bookies 
base data directory")
+    @Option(names = { "--bookkeeper-dir" }, description = "Local bookies base 
data directory")
     private String bkDir = "data/standalone/bookkeeper";
 
-    @Parameter(names = { "--no-broker" }, description = "Only start ZK and BK 
services, no broker")
+    @Option(names = { "--no-broker" }, description = "Only start ZK and BK 
services, no broker")
     private boolean noBroker = false;
 
-    @Parameter(names = { "--only-broker" }, description = "Only start Pulsar 
broker service (no ZK, BK)")
+    @Option(names = { "--only-broker" }, description = "Only start Pulsar 
broker service (no ZK, BK)")
     private boolean onlyBroker = false;
 
-    @Parameter(names = {"-nfw", "--no-functions-worker"}, description = "Run 
functions worker with Broker")
+    @Option(names = {"-nfw", "--no-functions-worker"}, description = "Run 
functions worker with Broker")
     private boolean noFunctionsWorker = false;
 
-    @Parameter(names = {"-fwc", "--functions-worker-conf"}, description = 
"Configuration file for Functions Worker")
+    @Option(names = {"-fwc", "--functions-worker-conf"}, description = 
"Configuration file for Functions Worker")
     private String fnWorkerConfigFile = "conf/functions_worker.yml";
 
-    @Parameter(names = {"-nss", "--no-stream-storage"}, description = "Disable 
stream storage")
+    @Option(names = {"-nss", "--no-stream-storage"}, description = "Disable 
stream storage")
     private boolean noStreamStorage = false;
 
-    @Parameter(names = { "--stream-storage-port" }, description = "Local 
bookies stream storage port")
+    @Option(names = { "--stream-storage-port" }, description = "Local bookies 
stream storage port")
     private int streamStoragePort = 4181;
 
-    @Parameter(names = { "-a", "--advertised-address" }, description = 
"Standalone broker advertised address")
+    @Option(names = { "-a", "--advertised-address" }, description = 
"Standalone broker advertised address")
     private String advertisedAddress = null;
 
-    @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+    @Option(names = { "-h", "--help" }, description = "Show this help message")
     private boolean help = false;
 
     private boolean usingNewDefaultsPIP117;
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java
index e3a5e66d4b6..0ab731591da 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java
@@ -19,8 +19,6 @@
 package org.apache.pulsar;
 
 import static org.apache.commons.lang3.StringUtils.isBlank;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import com.google.common.base.Strings;
 import java.io.FileInputStream;
 import java.util.Arrays;
@@ -30,23 +28,25 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.pulsar.broker.ServiceConfiguration;
 import org.apache.pulsar.common.configuration.PulsarConfigurationLoader;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
+import picocli.CommandLine;
+import picocli.CommandLine.Option;
 
 @Slf4j
 public class PulsarStandaloneStarter extends PulsarStandalone {
 
     private static final String PULSAR_CONFIG_FILE = "pulsar.config.file";
 
-    @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+    @Option(names = {"-g", "--generate-docs"}, description = "Generate docs")
     private boolean generateDocs = false;
 
     public PulsarStandaloneStarter(String[] args) throws Exception {
 
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(this);
+
         try {
-            jcommander.addObject(this);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (this.isHelp()) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 exit(0);
             }
             if (Strings.isNullOrEmpty(this.getConfigFile())) {
@@ -67,11 +67,11 @@ public class PulsarStandaloneStarter extends 
PulsarStandalone {
 
             if (this.isNoBroker() && this.isOnlyBroker()) {
                 log.error("Only one option is allowed between '--no-broker' 
and '--only-broker'");
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.usage(commander.getOut());
             log.error(e.getMessage());
             exit(1);
         }
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java
index 6aedfe13a5b..57b67b01191 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java
@@ -18,13 +18,15 @@
  */
 package org.apache.pulsar;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import org.apache.pulsar.broker.resources.PulsarResources;
 import org.apache.pulsar.common.naming.NamespaceName;
 import org.apache.pulsar.common.naming.SystemTopicNames;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
 import org.apache.pulsar.metadata.api.extended.MetadataStoreExtended;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Set up the transaction coordinator metadata for a cluster, the setup will 
create pulsar/system namespace and create
@@ -32,56 +34,56 @@ import 
org.apache.pulsar.metadata.api.extended.MetadataStoreExtended;
  */
 public class PulsarTransactionCoordinatorMetadataSetup {
 
+    @Command(name = "initialize-transaction-coordinator-metadata", 
showDefaultValues = true, scope = ScopeType.INHERIT)
     private static class Arguments {
 
-        @Parameter(names = { "-c", "--cluster" }, description = "Cluster 
name", required = true)
+        @Option(names = { "-c", "--cluster" }, description = "Cluster name", 
required = true)
         private String cluster;
 
-        @Parameter(names = { "-cs",
+        @Option(names = { "-cs",
                 "--configuration-store" }, description = "Configuration Store 
connection string", required = true)
         private String configurationStore;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--zookeeper-session-timeout-ms"
         }, description = "Local zookeeper session timeout ms")
         private int zkSessionTimeoutMillis = 30000;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--initial-num-transaction-coordinators"
         }, description = "Num transaction coordinators will assigned in 
cluster")
         private int numTransactionCoordinators = 16;
 
-        @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+        @Option(names = { "-h", "--help" }, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
     public static void main(String[] args) throws Exception {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
             if (arguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("initialize-transaction-coordinator-metadata", 
arguments);
+                cmd.addCommand("initialize-transaction-coordinator-metadata", 
commander);
                 cmd.run(null);
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.usage(commander.getOut());
             throw e;
         }
 
         if (arguments.configurationStore == null) {
             System.err.println("Configuration store address argument is 
required (--configuration-store)");
-            jcommander.usage();
+            commander.usage(commander.getOut());
             System.exit(1);
         }
 
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java
index 85a6c4156db..556b3ebfd84 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java
@@ -18,41 +18,43 @@
  */
 package org.apache.pulsar;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Pulsar version entry point.
  */
 public class PulsarVersionStarter {
 
+    @Command(name = "version", showDefaultValues = true, scope = 
ScopeType.INHERIT)
     private static class Arguments {
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
     public static void main(String[] args) {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
             if (arguments.generateDocs) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("version", arguments);
+                cmd.addCommand("version", commander);
                 cmd.run(null);
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.getErr().println(e);
             return;
         }
         System.out.println("Current version of pulsar is: " + 
PulsarVersion.getVersion());
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java
index 2a479ce4b90..980c92fee8a 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java
@@ -18,30 +18,32 @@
  */
 package org.apache.pulsar.broker.tools;
 
-import org.apache.bookkeeper.tools.framework.Cli;
-import org.apache.bookkeeper.tools.framework.CliFlags;
-import org.apache.bookkeeper.tools.framework.CliSpec;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * <b>broker-tool</b> is used for operations on a specific broker.
  */
+@Command(name = "broker-tool", description = "broker-tool is used for 
operations on a specific broker",
+        showDefaultValues = true, scope = ScopeType.INHERIT)
 public class BrokerTool {
 
-    public static final String NAME = "broker-tool";
+    @Option(
+            names = {"-h", "--help"},
+            description = "Display help information",
+            usageHelp = true
+    )
+    public boolean help = false;
 
     public static int run(String[] args) {
-        CliSpec.Builder<CliFlags> specBuilder = CliSpec.newBuilder()
-            .withName(NAME)
-            .withUsage(NAME + " [flags] [commands]")
-            .withDescription(NAME + " is used for operations on a specific 
broker")
-            .withFlags(new CliFlags())
-            .withConsole(System.out)
-            .addCommand(new LoadReportCommand())
-            .addCommand(new GenerateDocsCommand());
-
-        CliSpec<CliFlags> spec = specBuilder.build();
-
-        return Cli.runCli(spec, args);
+        BrokerTool brokerTool = new BrokerTool();
+        CommandLine commander = new CommandLine(brokerTool);
+        GenerateDocsCommand generateDocsCommand = new 
GenerateDocsCommand(commander);
+        commander.addSubcommand(LoadReportCommand.class)
+                .addSubcommand(generateDocsCommand);
+        return commander.execute(args);
     }
 
     public static void main(String[] args) {
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java
index b020b4bfd8b..b0ed54bc53f 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java
@@ -18,63 +18,42 @@
  */
 package org.apache.pulsar.broker.tools;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.bookkeeper.tools.framework.Cli;
-import org.apache.bookkeeper.tools.framework.CliCommand;
-import org.apache.bookkeeper.tools.framework.CliFlags;
-import org.apache.bookkeeper.tools.framework.CliSpec;
+import java.util.concurrent.Callable;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
 
 /**
  * The command to generate documents of broker-tool.
  */
-public class GenerateDocsCommand extends CliCommand<CliFlags, 
GenerateDocsCommand.GenDocFlags> {
+@Command(name = "gen-doc", description = "Generate documents of broker-tool")
+public class GenerateDocsCommand implements Callable<Integer> {
+    @Option(
+            names = {"-n", "--command-names"},
+            description = "List of command names",
+            arity = "0..1"
+    )
+    private List<String> commandNames = new ArrayList<>();
+    private final CommandLine rootCmd;
 
-    private static final String NAME = "gen-doc";
-    private static final String DESC = "Generate documents of broker-tool";
-
-    /**
-     * The CLI flags of gen docs command.
-     */
-    protected static class GenDocFlags extends CliFlags {
-        @Parameter(
-                names = {"-n", "--command-names"},
-                description = "List of command names"
-        )
-        private List<String> commandNames = new ArrayList<>();
-    }
-
-    public GenerateDocsCommand() {
-        super(CliSpec.<GenDocFlags>newBuilder()
-                .withName(NAME)
-                .withDescription(DESC)
-                .withFlags(new GenDocFlags())
-                .build());
+    public GenerateDocsCommand(CommandLine rootCmd) {
+        this.rootCmd = rootCmd;
     }
 
     @Override
-    public Boolean apply(CliFlags globalFlags, String[] args) {
-        CliSpec<GenDocFlags> newSpec = CliSpec.newBuilder(spec)
-                .withRunFunc(cmdFlags -> apply(cmdFlags))
-                .build();
-        return 0 == Cli.runCli(newSpec, args);
-    }
-
-    private boolean apply(GenerateDocsCommand.GenDocFlags flags) {
+    public Integer call() throws Exception {
         CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-        JCommander commander = new JCommander();
-        commander.addCommand("load-report", new LoadReportCommand.Flags());
-        cmd.addCommand("broker-tool", commander);
-        if (flags.commandNames.isEmpty()) {
+        cmd.addCommand("broker-tool", rootCmd);
+        if (commandNames.isEmpty()) {
             cmd.run(null);
         } else {
-            ArrayList<String> args = new ArrayList(flags.commandNames);
+            ArrayList<String> args = new ArrayList(commandNames);
             args.add(0, "-n");
             cmd.run(args.toArray(new String[0]));
         }
-        return true;
+        return 0;
     }
 }
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java
index 935e3a9f2fa..f1f4a917571 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java
@@ -18,76 +18,48 @@
  */
 package org.apache.pulsar.broker.tools;
 
-import com.beust.jcommander.Parameter;
 import java.util.Optional;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import org.apache.bookkeeper.tools.framework.Cli;
-import org.apache.bookkeeper.tools.framework.CliCommand;
-import org.apache.bookkeeper.tools.framework.CliFlags;
-import org.apache.bookkeeper.tools.framework.CliSpec;
 import org.apache.commons.lang3.SystemUtils;
 import org.apache.pulsar.broker.loadbalance.BrokerHostUsage;
 import org.apache.pulsar.broker.loadbalance.impl.GenericBrokerHostUsageImpl;
 import org.apache.pulsar.broker.loadbalance.impl.LinuxBrokerHostUsageImpl;
-import org.apache.pulsar.broker.tools.LoadReportCommand.Flags;
 import org.apache.pulsar.client.util.ExecutorProvider;
 import org.apache.pulsar.policies.data.loadbalancer.ResourceUsage;
 import org.apache.pulsar.policies.data.loadbalancer.SystemResourceUsage;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Spec;
 
 /**
  * The command to collect the load report of a specific broker.
  */
-public class LoadReportCommand extends CliCommand<CliFlags, Flags> {
+@Command(name = "load-report", description = "Collect the load report of a 
specific broker")
+public class LoadReportCommand implements Callable<Integer> {
 
-    private static final String NAME = "load-report";
-    private static final String DESC = "Collect the load report of a specific 
broker";
+    @Option(names = {"-i", "--interval-ms"}, description = "Interval to 
collect load report, in milliseconds")
+    public int intervalMilliseconds = 100;
 
-    /**
-     * The CLI flags of load report command.
-     */
-    public static class Flags extends CliFlags {
-
-        @Parameter(
-            names = {
-                "-i", "--interval-ms"
-            },
-            description = "Interval to collect load report, in milliseconds"
-        )
-        public int intervalMilliseconds = 100;
-
-    }
-
-    public LoadReportCommand() {
-        super(CliSpec.<Flags>newBuilder()
-            .withName(NAME)
-            .withDescription(DESC)
-            .withFlags(new Flags())
-            .build());
-    }
+    @Spec
+    CommandSpec spec;
 
     @Override
-    public Boolean apply(CliFlags globalFlags, String[] args) {
-        CliSpec<Flags> newSpec = CliSpec.newBuilder(spec)
-            .withRunFunc(cmdFlags -> apply(cmdFlags))
-            .build();
-        return 0 == Cli.runCli(newSpec, args);
-    }
-
-    private boolean apply(Flags flags) {
-
+    public Integer call() throws Exception {
         boolean isLinux = SystemUtils.IS_OS_LINUX;
-        spec.console().println("OS ARCH: " + SystemUtils.OS_ARCH);
-        spec.console().println("OS NAME: " + SystemUtils.OS_NAME);
-        spec.console().println("OS VERSION: " + SystemUtils.OS_VERSION);
-        spec.console().println("Linux: " + isLinux);
-        spec.console().println("--------------------------------------");
-        spec.console().println();
-        spec.console().println("Load Report Interval : " + 
flags.intervalMilliseconds + " ms");
-        spec.console().println();
-        spec.console().println("--------------------------------------");
-        spec.console().println();
+        spec.commandLine().getOut().println("OS ARCH: " + SystemUtils.OS_ARCH);
+        spec.commandLine().getOut().println("OS NAME: " + SystemUtils.OS_NAME);
+        spec.commandLine().getOut().println("OS VERSION: " + 
SystemUtils.OS_VERSION);
+        spec.commandLine().getOut().println("Linux: " + isLinux);
+        
spec.commandLine().getOut().println("--------------------------------------");
+        spec.commandLine().getOut().println();
+        spec.commandLine().getOut().println("Load Report Interval : " + 
intervalMilliseconds + " ms");
+        spec.commandLine().getOut().println();
+        
spec.commandLine().getOut().println("--------------------------------------");
+        spec.commandLine().getOut().println();
 
         ScheduledExecutorService scheduler = 
Executors.newSingleThreadScheduledExecutor(
                 new ExecutorProvider.ExtendedThreadFactory("load-report"));
@@ -105,7 +77,7 @@ public class LoadReportCommand extends CliCommand<CliFlags, 
Flags> {
 
             hostUsage.calculateBrokerHostUsage();
             try {
-                TimeUnit.MILLISECONDS.sleep(flags.intervalMilliseconds);
+                TimeUnit.MILLISECONDS.sleep(intervalMilliseconds);
             } catch (InterruptedException e) {
             }
             hostUsage.calculateBrokerHostUsage();
@@ -117,13 +89,13 @@ public class LoadReportCommand extends 
CliCommand<CliFlags, Flags> {
             printResourceUsage("Bandwidth In", usage.bandwidthIn);
             printResourceUsage("Bandwidth Out", usage.bandwidthOut);
 
-            return true;
+            return 0;
         } finally {
             scheduler.shutdown();
         }
     }
 
     private void printResourceUsage(String name, ResourceUsage usage) {
-        spec.console().println(name + " : usage = " + usage.usage + ", limit = 
" + usage.limit);
+        spec.commandLine().getOut().println(name + " : usage = " + usage.usage 
+ ", limit = " + usage.limit);
     }
 }
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java 
b/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java
index 83ff7902281..f8cc95e6ac0 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java
@@ -20,8 +20,6 @@ package org.apache.pulsar.compaction;
 
 import static org.apache.commons.lang3.StringUtils.isBlank;
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import io.netty.channel.EventLoopGroup;
 import io.netty.util.concurrent.DefaultThreadFactory;
@@ -48,20 +46,25 @@ import 
org.apache.pulsar.metadata.api.extended.MetadataStoreExtended;
 import org.apache.pulsar.policies.data.loadbalancer.AdvertisedListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
+@Command(name = "compact-topic", showDefaultValues = true, scope = 
ScopeType.INHERIT)
 public class CompactorTool {
 
     private static class Arguments {
-        @Parameter(names = {"-c", "--broker-conf"}, description = 
"Configuration file for Broker")
+        @Option(names = {"-c", "--broker-conf"}, description = "Configuration 
file for Broker")
         private String brokerConfigFile = "conf/broker.conf";
 
-        @Parameter(names = {"-t", "--topic"}, description = "Topic to 
compact", required = true)
+        @Option(names = {"-t", "--topic"}, description = "Topic to compact", 
required = true)
         private String topic;
 
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
@@ -107,13 +110,11 @@ public class CompactorTool {
 
     public static void main(String[] args) throws Exception {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander(arguments);
-        jcommander.setProgramName("PulsarTopicCompactor");
-
-        // parse args by JCommander
-        jcommander.parse(args);
+        CommandLine commander = new CommandLine(arguments);
+        commander.setCommandName("PulsarTopicCompactor");
+        commander.parseArgs(args);
         if (arguments.help) {
-            jcommander.usage();
+            commander.usage(commander.getOut());
             System.exit(0);
         }
 
@@ -126,7 +127,7 @@ public class CompactorTool {
 
         // init broker config
         if (isBlank(arguments.brokerConfigFile)) {
-            jcommander.usage();
+            commander.usage(commander.getOut());
             throw new IllegalArgumentException("Need to specify a 
configuration file for broker");
         }
 
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java
index fa3a7bed8f6..4ae28b2c0bd 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java
@@ -18,11 +18,7 @@
  */
 package org.apache.pulsar.utils.auth.tokens;
 
-import com.beust.jcommander.DefaultUsageFormatter;
-import com.beust.jcommander.IUsageFormatter;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
+import com.google.common.annotations.VisibleForTesting;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwt;
 import io.jsonwebtoken.Jwts;
@@ -31,7 +27,6 @@ import io.jsonwebtoken.io.Decoders;
 import io.jsonwebtoken.io.Encoders;
 import io.jsonwebtoken.security.Keys;
 import java.io.BufferedReader;
-import java.io.IOException;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -40,34 +35,42 @@ import java.security.Key;
 import java.security.KeyPair;
 import java.util.Date;
 import java.util.Optional;
+import java.util.concurrent.Callable;
 import javax.crypto.SecretKey;
 import lombok.Cleanup;
 import org.apache.pulsar.broker.authentication.utils.AuthTokenUtils;
-import org.apache.pulsar.cli.converters.TimeUnitToSecondsConverter;
+import org.apache.pulsar.cli.converters.picocli.TimeUnitToSecondsConverter;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+import picocli.CommandLine.ScopeType;
 
+@Command(name = "tokens", showDefaultValues = true, scope = ScopeType.INHERIT)
 public class TokensCliUtils {
 
-    public static class Arguments {
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
-        private boolean help = false;
-    }
+    private final CommandLine commander;
+
+    @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show 
this help message")
+    private boolean help;
 
-    @Parameters(commandDescription = "Create a new secret key")
-    public static class CommandCreateSecretKey {
-        @Parameter(names = {"-a",
+    @Command(description = "Create a new secret key")
+    public static class CommandCreateSecretKey implements Callable<Integer> {
+        @Option(names = {"-a",
                 "--signature-algorithm"}, description = "The signature 
algorithm for the new secret key.")
         SignatureAlgorithm algorithm = SignatureAlgorithm.HS256;
 
-        @Parameter(names = {"-o",
+        @Option(names = {"-o",
                 "--output"}, description = "Write the secret key to a file 
instead of stdout")
         String outputFile;
 
-        @Parameter(names = {
+        @Option(names = {
                 "-b", "--base64"}, description = "Encode the key in base64")
         boolean base64 = false;
 
-        public void run() throws IOException {
+        @Override
+        public Integer call() throws Exception {
             SecretKey secretKey = AuthTokenUtils.createSecretKey(algorithm);
             byte[] encoded = secretKey.getEncoded();
 
@@ -80,67 +83,73 @@ public class TokensCliUtils {
             } else {
                 System.out.write(encoded);
             }
+
+            return 0;
         }
     }
 
-    @Parameters(commandDescription = "Create a new or pair of keys 
public/private")
-    public static class CommandCreateKeyPair {
-        @Parameter(names = {"-a",
+    @Command(description = "Create a new or pair of keys public/private")
+    public static class CommandCreateKeyPair implements Callable<Integer> {
+        @Option(names = {"-a",
                 "--signature-algorithm"}, description = "The signature 
algorithm for the new key pair.")
         SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;
 
-        @Parameter(names = {
+        @Option(names = {
                 "--output-private-key"}, description = "File where to write 
the private key", required = true)
         String privateKeyFile;
-        @Parameter(names = {
+        @Option(names = {
                 "--output-public-key"}, description = "File where to write the 
public key", required = true)
         String publicKeyFile;
 
-        public void run() throws IOException {
+        @Override
+        public Integer call() throws Exception {
             KeyPair pair = Keys.keyPairFor(algorithm);
 
             Files.write(Paths.get(publicKeyFile), 
pair.getPublic().getEncoded());
             Files.write(Paths.get(privateKeyFile), 
pair.getPrivate().getEncoded());
+
+            return 0;
         }
     }
 
-    @Parameters(commandDescription = "Create a new token")
-    public static class CommandCreateToken {
-        @Parameter(names = {"-a",
+    @Command(description = "Create a new token")
+    public static class CommandCreateToken implements Callable<Integer> {
+        @Option(names = {"-a",
                 "--signature-algorithm"}, description = "The signature 
algorithm for the new key pair.")
         SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;
 
-        @Parameter(names = {"-s",
+        @Option(names = {"-s",
                 "--subject"},
                 description = "Specify the 'subject' or 'principal' associate 
with this token", required = true)
         private String subject;
 
-        @Parameter(names = {"-e",
+        @Option(names = {"-e",
                 "--expiry-time"},
                 description = "Relative expiry time for the token (eg: 1h, 3d, 
10y)."
                         + " (m=minutes) Default: no expiration",
-                    converter = TimeUnitToSecondsConverter.class)
+                converter = TimeUnitToSecondsConverter.class)
         private Long expiryTime = null;
 
-        @Parameter(names = {"-sk",
+        @Option(names = {"-sk",
                 "--secret-key"},
                 description = "Pass the secret key for signing the token. This 
can either be: data:, file:, etc..")
         private String secretKey;
 
-        @Parameter(names = {"-pk",
+        @Option(names = {"-pk",
                 "--private-key"},
                 description = "Pass the private key for signing the token. 
This can either be: data:, file:, etc..")
         private String privateKey;
 
-        public void run() throws Exception {
+        @Override
+        public Integer call() throws Exception {
             if (secretKey == null && privateKey == null) {
                 System.err.println(
                         "Either --secret-key or --private-key needs to be 
passed for signing a token");
-                System.exit(1);
+                return 1;
             } else if (secretKey != null && privateKey != null) {
                 System.err.println(
                         "Only one of --secret-key and --private-key needs to 
be passed for signing a token");
-                System.exit(1);
+                return 1;
             }
 
             Key signingKey;
@@ -159,27 +168,30 @@ public class TokensCliUtils {
 
             String token = AuthTokenUtils.createToken(signingKey, subject, 
optExpiryTime);
             System.out.println(token);
+
+            return 0;
         }
     }
 
-    @Parameters(commandDescription = "Show the content of token")
-    public static class CommandShowToken {
+    @Command(description = "Show the content of token")
+    public static class CommandShowToken implements Callable<Integer> {
 
-        @Parameter(description = "The token string", arity = 1)
-        private java.util.List<String> args;
+        @Parameters(description = "The token string", arity = "0..1")
+        private String args;
 
-        @Parameter(names = {"-i",
+        @Option(names = {"-i",
                 "--stdin"}, description = "Read token from standard input")
         private Boolean stdin = false;
 
-        @Parameter(names = {"-f",
+        @Option(names = {"-f",
                 "--token-file"}, description = "Read token from a file")
         private String tokenFile;
 
-        public void run() throws Exception {
+        @Override
+        public Integer call() throws Exception {
             String token;
             if (args != null) {
-                token = args.get(0);
+                token = args;
             } else if (stdin) {
                 @Cleanup
                 BufferedReader r = new BufferedReader(new 
InputStreamReader(System.in));
@@ -192,59 +204,61 @@ public class TokensCliUtils {
                 System.err.println(
                         "Token needs to be either passed as an argument or 
through `--stdin`,"
                                 + " `--token-file` or by the `TOKEN` 
environment variable");
-                System.exit(1);
-                return;
+                return 1;
             }
 
             String[] parts = token.split("\\.");
             System.out.println(new 
String(Decoders.BASE64URL.decode(parts[0])));
             System.out.println("---");
             System.out.println(new 
String(Decoders.BASE64URL.decode(parts[1])));
+
+            return 0;
         }
     }
 
-    @Parameters(commandDescription = "Validate a token against a key")
-    public static class CommandValidateToken {
+    @Command(description = "Validate a token against a key")
+    public static class CommandValidateToken implements Callable<Integer> {
 
-        @Parameter(names = {"-a",
+        @Option(names = {"-a",
                 "--signature-algorithm"}, description = "The signature 
algorithm for the key pair if using public key.")
         SignatureAlgorithm algorithm = SignatureAlgorithm.RS256;
 
-        @Parameter(description = "The token string", arity = 1)
-        private java.util.List<String> args;
+        @Parameters(description = "The token string", arity = "0..1")
+        private String args;
 
-        @Parameter(names = {"-i",
+        @Option(names = {"-i",
                 "--stdin"}, description = "Read token from standard input")
         private Boolean stdin = false;
 
-        @Parameter(names = {"-f",
+        @Option(names = {"-f",
                 "--token-file"}, description = "Read token from a file")
         private String tokenFile;
 
-        @Parameter(names = {"-sk",
+        @Option(names = {"-sk",
                 "--secret-key"},
                 description = "Pass the secret key for validating the token. 
This can either be: data:, file:, etc..")
         private String secretKey;
 
-        @Parameter(names = {"-pk",
+        @Option(names = {"-pk",
                 "--public-key"},
                 description = "Pass the public key for validating the token. 
This can either be: data:, file:, etc..")
         private String publicKey;
 
-        public void run() throws Exception {
+        @Override
+        public Integer call() throws Exception {
             if (secretKey == null && publicKey == null) {
                 System.err.println(
                         "Either --secret-key or --public-key needs to be 
passed for signing a token");
-                System.exit(1);
+                return 1;
             } else if (secretKey != null && publicKey != null) {
                 System.err.println(
                         "Only one of --secret-key and --public-key needs to be 
passed for signing a token");
-                System.exit(1);
+                return 1;
             }
 
             String token;
             if (args != null) {
-                token = args.get(0);
+                token = args;
             } else if (stdin) {
                 @Cleanup
                 BufferedReader r = new BufferedReader(new 
InputStreamReader(System.in));
@@ -257,8 +271,7 @@ public class TokensCliUtils {
                 System.err.println(
                         "Token needs to be either passed as an argument or 
through `--stdin`,"
                                 + " `--token-file` or by the `TOKEN` 
environment variable");
-                System.exit(1);
-                return;
+                return 1;
             }
 
             Key validationKey;
@@ -279,64 +292,46 @@ public class TokensCliUtils {
                     .parse(token);
 
             System.out.println(jwt.getBody());
+            return 0;
         }
     }
 
-    public static void main(String[] args) throws Exception {
-        Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander(arguments);
-        IUsageFormatter usageFormatter = new DefaultUsageFormatter(jcommander);
-
-        CommandCreateSecretKey commandCreateSecretKey = new 
CommandCreateSecretKey();
-        jcommander.addCommand("create-secret-key", commandCreateSecretKey);
-
-        CommandCreateKeyPair commandCreateKeyPair = new CommandCreateKeyPair();
-        jcommander.addCommand("create-key-pair", commandCreateKeyPair);
-
-        CommandCreateToken commandCreateToken = new CommandCreateToken();
-        jcommander.addCommand("create", commandCreateToken);
-
-        CommandShowToken commandShowToken = new CommandShowToken();
-        jcommander.addCommand("show", commandShowToken);
+    @Command
+    static class GenDoc implements Callable<Integer> {
 
-        CommandValidateToken commandValidateToken = new CommandValidateToken();
-        jcommander.addCommand("validate", commandValidateToken);
+        private final CommandLine rootCmd;
 
-        jcommander.addCommand("gen-doc", new Object());
-
-        try {
-            jcommander.parse(args);
-
-            if (arguments.help || jcommander.getParsedCommand() == null) {
-                jcommander.usage();
-                System.exit(1);
-            }
-        } catch (Exception e) {
-            System.err.println(e);
-            String chosenCommand = jcommander.getParsedCommand();
-            usageFormatter.usage(chosenCommand);
-            System.exit(1);
+        public GenDoc(CommandLine rootCmd) {
+            this.rootCmd = rootCmd;
         }
 
-        String cmd = jcommander.getParsedCommand();
-
-        if (cmd.equals("create-secret-key")) {
-            commandCreateSecretKey.run();
-        } else if (cmd.equals("create-key-pair")) {
-            commandCreateKeyPair.run();
-        } else if (cmd.equals("create")) {
-            commandCreateToken.run();
-        } else if (cmd.equals("show")) {
-            commandShowToken.run();
-        } else if (cmd.equals("validate")) {
-            commandValidateToken.run();
-        } else if (cmd.equals("gen-doc")) {
+        @Override
+        public Integer call() throws Exception {
             CmdGenerateDocs genDocCmd = new CmdGenerateDocs("pulsar");
-            genDocCmd.addCommand("tokens", jcommander);
+            genDocCmd.addCommand("tokens", rootCmd);
             genDocCmd.run(null);
-        } else {
-            System.err.println("Invalid command: " + cmd);
-            System.exit(1);
+
+            return 0;
         }
     }
+
+    TokensCliUtils() {
+        commander = new CommandLine(this);
+        commander.addSubcommand("create-secret-key", 
CommandCreateSecretKey.class);
+        commander.addSubcommand("create-key-pair", CommandCreateKeyPair.class);
+        commander.addSubcommand("create", CommandCreateToken.class);
+        commander.addSubcommand("show", CommandShowToken.class);
+        commander.addSubcommand("validate", CommandValidateToken.class);
+        commander.addSubcommand("gen-doc", new GenDoc(commander));
+    }
+
+    @VisibleForTesting
+    int execute(String[] args) {
+        return commander.execute(args);
+    }
+
+    public static void main(String[] args) throws Exception {
+        TokensCliUtils tokensCliUtils = new TokensCliUtils();
+        System.exit(tokensCliUtils.execute(args));
+    }
 }
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java
index 1bc3bd26f12..4c05a991b7f 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java
@@ -22,7 +22,6 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
-import com.beust.jcommander.Parameter;
 import com.google.common.collect.Sets;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -32,14 +31,16 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintStream;
 import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
+import java.io.StringWriter;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
+import lombok.Cleanup;
+import org.apache.pulsar.PulsarBrokerStarter.BrokerStarter;
 import org.apache.pulsar.broker.ServiceConfiguration;
-import org.apache.pulsar.docs.tools.CmdGenerateDocs;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 @Test(groups = "broker")
 public class PulsarBrokerStarterTest {
@@ -282,12 +283,14 @@ public class PulsarBrokerStarterTest {
      */
     @Test
     public void testMainWithNoArgument() throws Exception {
-        try {
-            PulsarBrokerStarter.main(new String[0]);
-            fail("No argument to main should've raised FileNotFoundException 
for no broker config!");
-        } catch (FileNotFoundException e) {
-            // code should reach here.
-        }
+        BrokerStarter brokerStarter = new BrokerStarter();
+        @Cleanup
+        StringWriter err = new StringWriter();
+        @Cleanup
+        PrintWriter printWriter = new PrintWriter(err);
+        brokerStarter.getCommander().setErr(printWriter);
+        assertEquals(brokerStarter.start(new String[0]), 1);
+        assertTrue(err.toString().contains("FileNotFoundException"));
     }
 
     /**
@@ -296,16 +299,16 @@ public class PulsarBrokerStarterTest {
      */
     @Test
     public void testMainRunBookieAndAutoRecoveryNoConfig() throws Exception {
-        try {
-            File testConfigFile = createValidBrokerConfigFile();
-            String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", 
"-ra", "-bc", ""};
-            PulsarBrokerStarter.main(args);
-            fail("No Config file for bookie auto recovery should've raised 
IllegalArgumentException!");
-        } catch (IllegalArgumentException e) {
-            // code should reach here.
-            e.printStackTrace();
-            assertEquals(e.getMessage(), "No configuration file for Bookie");
-        }
+        File testConfigFile = createValidBrokerConfigFile();
+        String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-ra", 
"-bc", ""};
+        BrokerStarter starter = new BrokerStarter();
+        @Cleanup
+        StringWriter err = new StringWriter();
+        @Cleanup
+        PrintWriter printWriter = new PrintWriter(err);
+        starter.getCommander().setErr(printWriter);
+        assertEquals(starter.start(args), 1);
+        assertTrue(err.toString().contains("No configuration file for 
Bookie"));
     }
 
     /**
@@ -314,15 +317,16 @@ public class PulsarBrokerStarterTest {
      */
     @Test
     public void testMainRunBookieRecoveryNoConfig() throws Exception {
-        try {
-            File testConfigFile = createValidBrokerConfigFile();
-            String[] args = {"-c", testConfigFile.getAbsolutePath(), "-ra", 
"-bc", ""};
-            PulsarBrokerStarter.main(args);
-            fail("No Config file for bookie auto recovery should've raised 
IllegalArgumentException!");
-        } catch (IllegalArgumentException e) {
-            // code should reach here.
-            assertEquals(e.getMessage(), "No configuration file for Bookie");
-        }
+        File testConfigFile = createValidBrokerConfigFile();
+        String[] args = {"-c", testConfigFile.getAbsolutePath(), "-ra", "-bc", 
""};
+        BrokerStarter starter = new BrokerStarter();
+        @Cleanup
+        StringWriter err = new StringWriter();
+        @Cleanup
+        PrintWriter printWriter = new PrintWriter(err);
+        starter.getCommander().setErr(printWriter);
+        assertEquals(starter.start(args), 1);
+        assertTrue(err.toString().contains("No configuration file for 
Bookie"));
     }
 
     /**
@@ -330,15 +334,16 @@ public class PulsarBrokerStarterTest {
      */
     @Test
     public void testMainRunBookieNoConfig() throws Exception {
-        try {
-            File testConfigFile = createValidBrokerConfigFile();
-            String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", 
"-bc", ""};
-            PulsarBrokerStarter.main(args);
-            fail("No Config file for bookie should've raised 
IllegalArgumentException!");
-        } catch (IllegalArgumentException e) {
-            // code should reach here
-            assertEquals(e.getMessage(), "No configuration file for Bookie");
-        }
+        File testConfigFile = createValidBrokerConfigFile();
+        String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-bc", 
""};
+        BrokerStarter starter = new BrokerStarter();
+        @Cleanup
+        StringWriter err = new StringWriter();
+        @Cleanup
+        PrintWriter printWriter = new PrintWriter(err);
+        starter.getCommander().setErr(printWriter);
+        assertEquals(starter.start(args), 1);
+        assertTrue(err.toString().contains("No configuration file for 
Bookie"));
     }
 
     /**
@@ -346,14 +351,16 @@ public class PulsarBrokerStarterTest {
      */
     @Test
     public void testMainEnableRunBookieThroughBrokerConfig() throws Exception {
-        try {
-            File testConfigFile = createValidBrokerConfigFile();
-            String[] args = {"-c", testConfigFile.getAbsolutePath()};
-            PulsarBrokerStarter.main(args);
-            fail("No argument to main should've raised 
IllegalArgumentException for no bookie config!");
-        } catch (IllegalArgumentException e) {
-            // code should reach here.
-        }
+        File testConfigFile = createValidBrokerConfigFile();
+        String[] args = {"-c", testConfigFile.getAbsolutePath()};
+        BrokerStarter starter = new BrokerStarter();
+        @Cleanup
+        StringWriter err = new StringWriter();
+        @Cleanup
+        PrintWriter printWriter = new PrintWriter(err);
+        starter.getCommander().setErr(printWriter);
+        assertEquals(starter.start(args), 1);
+        assertTrue(err.toString().contains("IllegalArgumentException"));
     }
 
     @Test
@@ -364,21 +371,15 @@ public class PulsarBrokerStarterTest {
             System.setOut(new PrintStream(baoStream));
 
             Class argumentsClass = 
Class.forName("org.apache.pulsar.PulsarBrokerStarter$StarterArguments");
-            Constructor constructor = argumentsClass.getDeclaredConstructor();
-            constructor.setAccessible(true);
-            Object obj = constructor.newInstance();
-
-            CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-            cmd.addCommand("broker", obj);
-            cmd.run(null);
+            PulsarBrokerStarter.main(new String[]{"-g"});
 
             String message = baoStream.toString();
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     String nameStr = Arrays.asList(names).toString();
                     nameStr = nameStr.substring(1, nameStr.length() - 1);
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java
index 6196f666988..710e040f8df 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java
@@ -19,13 +19,15 @@
 package org.apache.pulsar;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import lombok.extern.slf4j.Slf4j;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
+@Slf4j
 public class PulsarClusterMetadataSetupTest {
     @Test
     public void testMainGenerateDocs() throws Exception {
@@ -43,16 +45,16 @@ public class PulsarClusterMetadataSetupTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
-                    if (names.length == 0) {
+                    if (names.length == 0 || fieldAnno.hidden()) {
                         continue;
                     }
                     String nameStr = Arrays.asList(names).toString();
                     nameStr = nameStr.substring(1, nameStr.length() - 1);
-                    assertTrue(message.indexOf(nameStr) > 0);
+                    assertTrue(message.indexOf(nameStr) > 0, nameStr);
                 }
             }
         } finally {
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java
index f6a388dac76..95d12f378c5 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class PulsarClusterMetadataTeardownTest {
     @Test
@@ -43,9 +43,9 @@ public class PulsarClusterMetadataTeardownTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     if (names.length == 0) {
                         continue;
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java
index c1ad8c621c4..0c6ba05b460 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class PulsarInitialNamespaceSetupTest {
     @Test
@@ -43,9 +43,9 @@ public class PulsarInitialNamespaceSetupTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     if (names.length == 0) {
                         continue;
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java
index 70c7c7bd62e..6ff055385b2 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class PulsarTransactionCoordinatorMetadataSetupTest {
     @Test
@@ -43,9 +43,9 @@ public class PulsarTransactionCoordinatorMetadataSetupTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     if (names.length == 0) {
                         continue;
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java
index 219e3b80cd3..b921c3d3843 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class PulsarVersionStarterTest {
     @Test
@@ -43,9 +43,9 @@ public class PulsarVersionStarterTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     if (names.length == 0) {
                         continue;
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java
index ad2cf7784eb..063c041f2e0 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar.broker.tools;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 /**
  * Broker Tool Tests.
@@ -47,12 +47,12 @@ public class BrokerToolTest {
 
             String message = baoStream.toString();
 
-            Class argumentsClass = 
Class.forName("org.apache.pulsar.broker.tools.LoadReportCommand$Flags");
+            Class argumentsClass = 
Class.forName("org.apache.pulsar.broker.tools.LoadReportCommand");
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     String nameStr = Arrays.asList(names).toString();
                     nameStr = nameStr.substring(1, nameStr.length() - 1);
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java
index fb8d6566d9a..72b8628caca 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Constructor;
@@ -37,6 +36,7 @@ import org.apache.pulsar.client.api.PulsarClient;
 import org.apache.pulsar.client.api.PulsarClientException;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 /**
  * CompactorTool Tests.
@@ -69,9 +69,9 @@ public class CompactorToolTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     String nameStr = Arrays.asList(names).toString();
                     nameStr = nameStr.substring(1, nameStr.length() - 1);
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java
index a488e4d9584..d5dc259438e 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar.utils.auth.tokens;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 /**
  * TokensCliUtils Tests.
@@ -43,7 +43,7 @@ public class TokensCliUtilsTest {
             ByteArrayOutputStream baoStream = new ByteArrayOutputStream();
             System.setOut(new PrintStream(baoStream));
 
-            TokensCliUtils.main(new String[]{"gen-doc"});
+            new TokensCliUtils().execute(new String[]{"gen-doc"});
 
             String message = baoStream.toString();
 
@@ -68,9 +68,9 @@ public class TokensCliUtilsTest {
         Class argumentsClass = Class.forName(className);
         Field[] fields = argumentsClass.getDeclaredFields();
         for (Field field : fields) {
-            boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class);
+            boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
             if (fieldHasAnno) {
-                Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                Option fieldAnno = field.getAnnotation(Option.class);
                 String[] names = fieldAnno.names();
                 if (names.length < 1) {
                     continue;
diff --git a/pulsar-docs-tools/pom.xml b/pulsar-docs-tools/pom.xml
index 40bddfde532..e275d128fb0 100644
--- a/pulsar-docs-tools/pom.xml
+++ b/pulsar-docs-tools/pom.xml
@@ -43,8 +43,8 @@
             <artifactId>swagger-core</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.beust</groupId>
-            <artifactId>jcommander</artifactId>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli</artifactId>
         </dependency>
     </dependencies>
 
diff --git 
a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java
 
b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java
index db6178a7fda..ff474d98edc 100644
--- 
a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java
+++ 
b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java
@@ -18,8 +18,6 @@
  */
 package org.apache.pulsar.docs.tools;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import io.swagger.annotations.ApiModelProperty;
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
@@ -28,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.Callable;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import lombok.SneakyThrows;
@@ -35,49 +34,44 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.MethodUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 @Slf4j
-public abstract class BaseGenerateDocumentation {
+@Command(name = "gen-doc", showDefaultValues = true, scope = ScopeType.INHERIT)
+public abstract class BaseGenerateDocumentation implements Callable<Integer> {
 
-    JCommander jcommander;
+    CommandLine commander;
 
-    @Parameter(names = {"-c", "--class-names"}, description =
+    @Option(names = {"-c", "--class-names"}, description =
             "List of class names, generate documentation based on the 
annotations in the Class")
     private List<String> classNames = new ArrayList<>();
 
-    @Parameter(names = {"-h", "--help"}, help = true, description = "Show this 
help.")
+    @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show 
this help.")
     boolean help;
 
     public BaseGenerateDocumentation() {
-        jcommander = new JCommander();
-        jcommander.setProgramName("pulsar-generateDocumentation");
-        jcommander.addObject(this);
+        commander = new CommandLine(this);
     }
 
-    public boolean run(String[] args) throws Exception {
-        if (args.length == 0) {
-            jcommander.usage();
-            return false;
-        }
-
-        if (help) {
-            jcommander.usage();
-            return true;
-        }
-
-        try {
-            jcommander.parse(Arrays.copyOfRange(args, 0, args.length));
-        } catch (Exception e) {
-            System.err.println(e.getMessage());
-            jcommander.usage();
-            return false;
-        }
+    @Override
+    public Integer call() throws Exception {
         if (classNames != null) {
             for (String className : classNames) {
                 System.out.println(generateDocumentByClassName(className));
             }
         }
-        return true;
+        return 0;
+    }
+
+    public boolean run(String[] args) throws Exception {
+        if (args.length == 0) {
+            commander.usage(commander.getOut());
+            return false;
+        }
+        return commander.execute(args) == 0;
     }
 
     protected abstract String generateDocumentByClassName(String className) 
throws Exception;
diff --git 
a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java
 
b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java
index 8f784c1eca1..a66da9fd6c6 100644
--- 
a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java
+++ 
b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java
@@ -18,144 +18,155 @@
  */
 package org.apache.pulsar.docs.tools;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterDescription;
-import com.beust.jcommander.Parameters;
+import com.google.common.annotations.VisibleForTesting;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
+import java.util.concurrent.Callable;
 import lombok.Getter;
 import lombok.Setter;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.ArgSpec;
+import picocli.CommandLine.Model.OptionSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 @Getter
 @Setter
-@Parameters(commandDescription = "Generate documentation automatically.")
-public class CmdGenerateDocs {
+@Command(showDefaultValues = true, scope = ScopeType.INHERIT)
+public class CmdGenerateDocs implements Callable<Integer> {
 
-    @Parameter(
+    @Option(
             names = {"-h", "--help"},
-            description = "Display help information"
+            description = "Display help information",
+            usageHelp = true
     )
     public boolean help;
 
-    @Parameter(
+    @Option(
             names = {"-n", "--command-names"},
             description = "List of command names"
     )
     private List<String> commandNames = new ArrayList<>();
 
     private static final String name = "gen-doc";
-    private final JCommander jcommander;
+    private final CommandLine commander;
 
     public CmdGenerateDocs(String cmdName) {
-        jcommander = new JCommander(this);
-        jcommander.setProgramName(cmdName);
+        commander = new CommandLine(this);
+        commander.setCommandName(cmdName);
     }
 
     public CmdGenerateDocs addCommand(String name, Object command) {
-        jcommander.addCommand(name, command);
+        commander.addSubcommand(name, command);
         return this;
     }
 
     public boolean run(String[] args) {
-        JCommander tmpCmd = new JCommander(this);
-        tmpCmd.setProgramName(jcommander.getProgramName() + " " + name);
-        try {
-            if (args == null) {
-                args = new String[]{};
-            }
-            tmpCmd.parse(args);
-        } catch (Exception e) {
-            System.err.println(e.getMessage());
-            System.err.println();
-            tmpCmd.usage();
-            return false;
+        if (args == null) {
+            args = new String[]{};
         }
-        if (help) {
-            tmpCmd.usage();
-            return true;
+        return commander.execute(args) == 0;
+    }
+
+    private static String getCommandDescription(CommandLine commandLine) {
+        String[] description = 
commandLine.getCommandSpec().usageMessage().description();
+        if (description != null && description.length != 0) {
+            return description[0];
         }
+        return "";
+    }
 
-        if (commandNames.size() == 0) {
-            for (Map.Entry<String, JCommander> cmd : 
jcommander.getCommands().entrySet()) {
-                if (cmd.getKey().equals(name)) {
-                    continue;
-                }
-                System.out.println(generateDocument(cmd.getKey(), jcommander));
-            }
-        } else {
-            for (String commandName : commandNames) {
-                if (commandName.equals(name)) {
-                    continue;
-                }
-                if (!jcommander.getCommands().keySet().contains(commandName)) {
-                    continue;
-                }
-                System.out.println(generateDocument(commandName, jcommander));
-            }
+    private static String getArgDescription(ArgSpec argSpec) {
+        String[] description = argSpec.description();
+        if (description != null && description.length != 0) {
+            return description[0];
         }
-        return true;
+        return "";
     }
 
-    private String generateDocument(String module, JCommander commander) {
-        JCommander cmd = commander.getCommands().get(module);
+    private String generateDocument(String module, CommandLine commander) {
         StringBuilder sb = new StringBuilder();
         sb.append("# ").append(module).append("\n\n");
-        String desc = 
commander.getUsageFormatter().getCommandDescription(module);
+        String desc = getCommandDescription(commander);
         if (null != desc && !desc.isEmpty()) {
             sb.append(desc).append("\n");
         }
         sb.append("\n\n```shell\n")
                 .append("$ ");
-        if (null != jcommander.getProgramName() && 
!jcommander.getProgramName().isEmpty()) {
-            sb.append(jcommander.getProgramName()).append(" ");
-        }
-        sb.append(module);
-        if (cmd.getObjects().size() > 0
-                && 
cmd.getObjects().get(0).getClass().getName().equals("com.beust.jcommander.JCommander"))
 {
-            JCommander cmdObj = (JCommander) cmd.getObjects().get(0);
+        String commandName = commander.getCommandName();
+        sb.append(this.commander.getCommandName() + " " + commandName);
+        if (!commander.getSubcommands().isEmpty()) {
             sb.append(" subcommand").append("\n```").append("\n\n");
-            cmdObj.getCommands().forEach((subK, subV) -> {
+            commander.getSubcommands().forEach((subK, subV) -> {
                 if (!subK.equals(name)) {
                     sb.append("\n\n## ").append(subK).append("\n\n");
-                    String subDesc = 
cmdObj.getUsageFormatter().getCommandDescription(subK);
+                    String subDesc = getCommandDescription(subV);
                     if (null != subDesc && !subDesc.isEmpty()) {
                         sb.append(subDesc).append("\n");
                     }
                     sb.append("```shell\n$ ");
-                    if (null != jcommander.getProgramName() && 
!jcommander.getProgramName().isEmpty()) {
-                        sb.append(jcommander.getProgramName()).append(" ");
-                    }
+                    sb.append(this.commander.getCommandName()).append(" ");
                     sb.append(module).append(" ").append(subK).append(" 
options").append("\n```\n\n");
-                    List<ParameterDescription> options = 
cmdObj.getCommands().get(subK).getParameters();
-                    if (options.size() > 0) {
+                    List<ArgSpec> argSpecs = subV.getCommandSpec().args();
+                    if (argSpecs.size() > 0) {
                         sb.append("|Flag|Description|Default|\n");
                         sb.append("|---|---|---|\n");
                     }
-                    options.forEach((option) ->
-                            sb.append("| `").append(option.getNames())
-                                    .append("` | 
").append(option.getDescription().replace("\n", " "))
-                                    
.append("|").append(option.getDefault()).append("|\n")
-                    );
+
+                    argSpecs.forEach(option -> {
+                        if (option.hidden() || !(option instanceof 
OptionSpec)) {
+                            return;
+                        }
+                        sb.append("| `").append(String.join(", ", 
((OptionSpec) option).names()))
+                                .append("` | 
").append(getArgDescription(option).replace("\n", " "))
+                                
.append("|").append(option.defaultValueString()).append("|\n");
+                    });
                 }
             });
         } else {
             sb.append(" options").append("\n```").append("\n\n");
             sb.append("|Flag|Description|Default|\n");
             sb.append("|---|---|---|\n");
-            List<ParameterDescription> options = cmd.getParameters();
-            
options.stream().sorted(Comparator.comparing(ParameterDescription::getLongestName))
-                    .forEach((option) ->
-                            sb.append("| `")
-                                    .append(option.getNames())
-                                    .append("` | ")
-                                    
.append(option.getDescription().replace("\n", " "))
-                                    .append("|")
-                                    .append(option.getDefault()).append("|\n")
-                    );
+            List<ArgSpec> argSpecs = commander.getCommandSpec().args();
+            argSpecs.forEach(option -> {
+                if (option.hidden() || !(option instanceof OptionSpec)) {
+                    return;
+                }
+                sb.append("| `")
+                        .append(String.join(", ", ((OptionSpec) 
option).names()))
+                        .append("` | ")
+                        .append(getArgDescription(option).replace("\n", " "))
+                        .append("|")
+                        .append(option.defaultValueString()).append("|\n");
+            });
         }
         return sb.toString();
     }
+
+    @Override
+    public Integer call() throws Exception {
+        if (commandNames.size() == 0) {
+            commander.getSubcommands().forEach((name, cmd) -> {
+                commander.getOut().println(generateDocument(name, cmd));
+            });
+        } else {
+            for (String commandName : commandNames) {
+                if (commandName.equals(name)) {
+                    continue;
+                }
+                CommandLine cmd = commander.getSubcommands().get(commandName);
+                if (cmd == null) {
+                    continue;
+                }
+                commander.getOut().println(generateDocument(commandName, cmd));
+            }
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    CommandLine getCommander() {
+        return commander;
+    }
 }
diff --git 
a/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java
 
b/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java
index 3f96ddaef59..0f0f96f80ec 100644
--- 
a/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java
+++ 
b/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java
@@ -19,79 +19,63 @@
 package org.apache.pulsar.docs.tools;
 
 import static org.testng.Assert.assertEquals;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
 
 public class CmdGenerateDocsTest {
 
-    @Parameters(commandDescription = "Options")
+    @Command
     public class Arguments {
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-n", "--name"}, description = "Name")
+        @Option(names = {"-n", "--name"}, description = "Name")
         private String name;
     }
 
     @Test
     public void testHelp() {
-        PrintStream oldStream = System.out;
-        try {
-            ByteArrayOutputStream baoStream = new ByteArrayOutputStream(2048);
-            PrintStream cacheStream = new PrintStream(baoStream);
-            System.setOut(cacheStream);
+        CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
+        cmd.addCommand("test", new Arguments());
+        StringWriter stringWriter = new StringWriter();
+        cmd.getCommander().setOut(new PrintWriter(stringWriter));
+        cmd.run(new String[]{"-h"});
 
-            CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-            cmd.addCommand("test", new Arguments());
-            cmd.run(new String[]{"-h"});
-
-            String message = baoStream.toString();
-            String rightMsg = "Usage: pulsar gen-doc [options]\n"
-                    + "  Options:\n"
-                    + "    -n, --command-names\n"
-                    + "      List of command names\n"
-                    + "      Default: []\n"
-                    + "    -h, --help\n"
-                    + "      Display help information\n"
-                    + "      Default: false\n"
-                    + System.lineSeparator();
-            assertEquals(rightMsg, message);
-        } finally {
-            System.setOut(oldStream);
-        }
+        String message = stringWriter.toString();
+        String rightMsg = "Usage: pulsar [-h] [-n=<commandNames>]... 
[COMMAND]\n"
+                + "  -h, --help   Display help information\n"
+                + "  -n, --command-names=<commandNames>\n"
+                + "               List of command names\n"
+                + "                 Default: []\n"
+                + "Commands:\n"
+                + "  test\n";
+        assertEquals(message, rightMsg);
     }
 
     @Test
     public void testGenerateDocs() {
-        PrintStream oldStream = System.out;
-        try {
-            ByteArrayOutputStream baoStream = new ByteArrayOutputStream(2048);
-            PrintStream cacheStream = new PrintStream(baoStream);
-            System.setOut(cacheStream);
-
-            CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-            cmd.addCommand("test", new Arguments());
-            cmd.run(null);
+        CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
+        cmd.addCommand("test", new Arguments());
+        StringWriter stringWriter = new StringWriter();
+        cmd.getCommander().setOut(new PrintWriter(stringWriter));
+        cmd.run(null);
 
-            String message = baoStream.toString();
-            String rightMsg = "# test\n\n"
-                    + "Options\n\n"
-                    + "\n"
-                    + "```shell\n"
-                    + "$ pulsar test options\n"
-                    + "```\n"
-                    + "\n"
-                    + "|Flag|Description|Default|\n"
-                    + "|---|---|---|\n"
-                    + "| `-h, --help` | Show this help message|false|\n"
-                    + "| `-n, --name` | Name|null|\n"
-                    + System.lineSeparator();
-            assertEquals(rightMsg, message);
-        } finally {
-            System.setOut(oldStream);
-        }
+        String message = stringWriter.toString();
+        String rightMsg = "# test\n\n"
+                + "\n"
+                + "\n"
+                + "```shell\n"
+                + "$ pulsar test options\n"
+                + "```\n"
+                + "\n"
+                + "|Flag|Description|Default|\n"
+                + "|---|---|---|\n"
+                + "| `-h, --help` | Show this help message|false|\n"
+                + "| `-n, --name` | Name|null|\n"
+                + System.lineSeparator();
+        assertEquals(message, rightMsg);
     }
 }
diff --git 
a/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java
 
b/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java
index 679ce1db70d..c5fb552d9cc 100644
--- 
a/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java
+++ 
b/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java
@@ -19,11 +19,13 @@
 package org.apache.pulsar.functions.worker;
 
 import static org.apache.commons.lang3.StringUtils.isBlank;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.pulsar.common.util.ShutdownUtil;
 import org.apache.pulsar.docs.tools.CmdGenerateDocs;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * A starter to start function worker.
@@ -31,35 +33,36 @@ import org.apache.pulsar.docs.tools.CmdGenerateDocs;
 @Slf4j
 public class FunctionWorkerStarter {
 
+    @Command(name = "functions-worker", showDefaultValues = true, scope = 
ScopeType.INHERIT)
     private static class WorkerArguments {
-        @Parameter(
+        @Option(
             names = { "-c", "--conf" },
             description = "Configuration File for Function Worker")
         private String configFile;
 
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
+
     public static void main(String[] args) throws Exception {
         WorkerArguments workerArguments = new WorkerArguments();
-        JCommander commander = new JCommander(workerArguments);
-        commander.setProgramName("FunctionWorkerStarter");
+        CommandLine commander = new CommandLine(workerArguments);
+        commander.setCommandName("FunctionWorkerStarter");
 
-        // parse args by commander
-        commander.parse(args);
+        commander.parseArgs(args);
 
         if (workerArguments.help) {
-            commander.usage();
+            commander.usage(commander.getOut());
             return;
         }
 
         if (workerArguments.generateDocs) {
             CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-            cmd.addCommand("functions-worker", workerArguments);
+            cmd.addCommand("functions-worker", commander);
             cmd.run(null);
             return;
         }
diff --git 
a/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java
 
b/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java
index 51bcd974f50..6fa0bec1b36 100644
--- 
a/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java
+++ 
b/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar.functions.worker;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class FunctionWorkerStarterTest {
     @Test
@@ -43,9 +43,9 @@ public class FunctionWorkerStarterTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     String nameStr = Arrays.asList(names).toString();
                     nameStr = nameStr.substring(1, nameStr.length() - 1);
diff --git a/pulsar-io/docs/pom.xml b/pulsar-io/docs/pom.xml
index adbc2f4efad..0fd774aaafe 100644
--- a/pulsar-io/docs/pom.xml
+++ b/pulsar-io/docs/pom.xml
@@ -42,8 +42,8 @@
       <artifactId>reflections</artifactId>
     </dependency>
     <dependency>
-      <groupId>com.beust</groupId>
-      <artifactId>jcommander</artifactId>
+      <groupId>info.picocli</groupId>
+      <artifactId>picocli</artifactId>
     </dependency>
 
     <!-- include connectors -->
diff --git a/pulsar-proxy/pom.xml b/pulsar-proxy/pom.xml
index 55dfd11e40e..64ca301facf 100644
--- a/pulsar-proxy/pom.xml
+++ b/pulsar-proxy/pom.xml
@@ -184,8 +184,8 @@
     </dependency>
 
     <dependency>
-      <groupId>com.beust</groupId>
-      <artifactId>jcommander</artifactId>
+      <groupId>info.picocli</groupId>
+      <artifactId>picocli</artifactId>
     </dependency>
 
     <dependency>
diff --git 
a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java
 
b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java
index 1a98601f2a9..72d54601995 100644
--- 
a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java
+++ 
b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java
@@ -23,8 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
 import static org.apache.commons.lang3.StringUtils.isEmpty;
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
 import static org.apache.pulsar.common.stats.JvmMetrics.getJvmDirectMemoryUsed;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import com.google.common.annotations.VisibleForTesting;
 import io.prometheus.client.CollectorRegistry;
 import io.prometheus.client.Gauge;
@@ -61,40 +59,45 @@ import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ScopeType;
 
 /**
  * Starts an instance of the Pulsar ProxyService.
  */
+@Command(name = "proxy", showDefaultValues = true, scope = ScopeType.INHERIT)
 public class ProxyServiceStarter {
 
-    @Parameter(names = { "-c", "--config" }, description = "Configuration file 
path", required = true)
+    @Option(names = { "-c", "--config" }, description = "Configuration file 
path", required = true)
     private String configFile;
 
     @Deprecated
-    @Parameter(names = { "-zk", "--zookeeper-servers" },
+    @Option(names = { "-zk", "--zookeeper-servers" },
             description = "Local zookeeper connection string, please use 
--metadata-store instead")
     private String zookeeperServers = "";
-    @Parameter(names = { "-md", "--metadata-store" }, description = "Metadata 
Store service url. eg: zk:my-zk:2181")
+    @Option(names = { "-md", "--metadata-store" }, description = "Metadata 
Store service url. eg: zk:my-zk:2181")
     private String metadataStoreUrl = "";
 
     @Deprecated
-    @Parameter(names = { "-gzk", "--global-zookeeper-servers" },
+    @Option(names = { "-gzk", "--global-zookeeper-servers" },
             description = "Global zookeeper connection string, please use 
--configuration-metadata-store instead")
     private String globalZookeeperServers = "";
 
     @Deprecated
-    @Parameter(names = { "-cs", "--configuration-store-servers" },
+    @Option(names = { "-cs", "--configuration-store-servers" },
                     description = "Configuration store connection string, "
                             + "please use --configuration-metadata-store 
instead")
     private String configurationStoreServers = "";
-    @Parameter(names = { "-cms", "--configuration-metadata-store" },
+    @Option(names = { "-cms", "--configuration-metadata-store" },
             description = "The metadata store URL for the configuration data")
     private String configurationMetadataStoreUrl = "";
 
-    @Parameter(names = { "-h", "--help" }, description = "Show this help 
message")
+    @Option(names = { "-h", "--help" }, description = "Show this help message")
     private boolean help = false;
 
-    @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+    @Option(names = {"-g", "--generate-docs"}, description = "Generate docs")
     private boolean generateDocs = false;
 
     private ProxyConfiguration config;
@@ -116,23 +119,22 @@ public class ProxyServiceStarter {
                 exception.printStackTrace(System.out);
             });
 
-            JCommander jcommander = new JCommander();
+            CommandLine commander = new CommandLine(this);
             try {
-                jcommander.addObject(this);
-                jcommander.parse(args);
+                commander.parseArgs(args);
                 if (help || isBlank(configFile)) {
-                    jcommander.usage();
+                    commander.usage(commander.getOut());
                     return;
                 }
 
                 if (this.generateDocs) {
                     CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                    cmd.addCommand("proxy", this);
+                    cmd.addCommand("proxy", commander);
                     cmd.run(null);
                     System.exit(0);
                 }
             } catch (Exception e) {
-                jcommander.usage();
+                commander.getErr().println(e);
                 System.exit(1);
             }
 
diff --git 
a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java
 
b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java
index fd38208323c..ed1a99b8133 100644
--- 
a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java
+++ 
b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java
@@ -22,8 +22,6 @@ import static 
com.google.common.base.Preconditions.checkArgument;
 import static 
org.apache.pulsar.websocket.admin.WebSocketWebResource.ADMIN_PATH_V1;
 import static 
org.apache.pulsar.websocket.admin.WebSocketWebResource.ADMIN_PATH_V2;
 import static 
org.apache.pulsar.websocket.admin.WebSocketWebResource.ATTRIBUTE_PROXY_SERVICE_NAME;
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
 import org.apache.pulsar.common.configuration.PulsarConfigurationLoader;
 import org.apache.pulsar.common.configuration.VipStatus;
 import org.apache.pulsar.common.util.ShutdownUtil;
@@ -37,37 +35,42 @@ import 
org.apache.pulsar.websocket.admin.v1.WebSocketProxyStatsV1;
 import org.apache.pulsar.websocket.admin.v2.WebSocketProxyStatsV2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+import picocli.CommandLine.ScopeType;
 
 public class WebSocketServiceStarter {
+    @Command(name = "websocket", showDefaultValues = true, scope = 
ScopeType.INHERIT)
     private static class Arguments {
-        @Parameter(description = "config file")
+        @Parameters(description = "config file", arity = "0..1")
         private String configFile = "";
 
-        @Parameter(names = {"-h", "--help"}, description = "Show this help 
message")
+        @Option(names = {"-h", "--help"}, description = "Show this help 
message")
         private boolean help = false;
 
-        @Parameter(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
+        @Option(names = {"-g", "--generate-docs"}, description = "Generate 
docs")
         private boolean generateDocs = false;
     }
 
     public static void main(String[] args) throws Exception {
         Arguments arguments = new Arguments();
-        JCommander jcommander = new JCommander();
+        CommandLine commander = new CommandLine(arguments);
         try {
-            jcommander.addObject(arguments);
-            jcommander.parse(args);
+            commander.parseArgs(args);
             if (arguments.help) {
-                jcommander.usage();
+                commander.usage(commander.getOut());
                 return;
             }
             if (arguments.generateDocs && arguments.configFile != null) {
                 CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar");
-                cmd.addCommand("websocket", arguments);
+                cmd.addCommand("websocket", commander);
                 cmd.run(null);
                 return;
             }
         } catch (Exception e) {
-            jcommander.usage();
+            commander.getErr().println(e);
             return;
         }
 
diff --git 
a/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java
 
b/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java
index c898a07e228..f9a190cf866 100644
--- 
a/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java
+++ 
b/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java
@@ -19,12 +19,12 @@
 package org.apache.pulsar.websocket.service;
 
 import static org.testng.Assert.assertTrue;
-import com.beust.jcommander.Parameter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import org.testng.annotations.Test;
+import picocli.CommandLine.Option;
 
 public class WebSocketServiceStarterTest {
     @Test
@@ -43,9 +43,9 @@ public class WebSocketServiceStarterTest {
 
             Field[] fields = argumentsClass.getDeclaredFields();
             for (Field field : fields) {
-                boolean fieldHasAnno = 
field.isAnnotationPresent(Parameter.class);
+                boolean fieldHasAnno = field.isAnnotationPresent(Option.class);
                 if (fieldHasAnno) {
-                    Parameter fieldAnno = field.getAnnotation(Parameter.class);
+                    Option fieldAnno = field.getAnnotation(Option.class);
                     String[] names = fieldAnno.names();
                     if (names.length == 0) {
                         continue;

Reply via email to