[Bug] mqadmin: the declared option set does not match what the commands read (NPE instead of a usage error; the consumer -i mode is unreachable)

Open
#11,410 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@iloveeyjafjalla is already working on this.

Since Oct 13, 2026.

  • #11411 by @iloveeyjafjalla — open

Assessment

Difficulty
2/5
Estimated time
Half a day
Newbie friendliness
25/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
cli

Research direction

Start with the option declarations in tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerSubCommand.java, ResetMasterFlushOffsetSubCommand, and message/DecodeMessageIdCommond.java, comparing each Option set with the getOptionValue calls in execute(). Linked PR #11411 is already open against this issue, so check it before starting. Done means consumer -i parses, and the other two commands reject a missing -b/-o/-i at parse time.

Written by the indexing model from the issue text.

Description

Before Creating the Bug Report
  • I found a bug, not just asking a question, which should be created in GitHub Discussions.
  • I have searched the GitHub Issues and GitHub Discussions of this repository and believe that this is not a duplicate.
  • I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.
Runtime platform environment

OS: any (the mqadmin command line tool)

RocketMQ version

branch: develop
Git commit id: a7edeecfb

JDK Version

Compiler: Temurin 1.8.0_504 (target 1.8, same as CI)

Describe the Bug

I cross-checked every commons-cli Option a subcommand declares against the keys the same command reads (hasOption/getOptionValue) across the 73 SubCommand classes of the tools module. Apart from the static-topic -mf/--mapFile mismatch that is already reported in #11381, three commands still declare an option set that does not match what their execute() needs:

(1) mqadmin consumer: -i is read but never declared, so the single-client mode cannot be used.

ConsumerSubCommand.buildCommandlineOptions only registers -g/--consumerGroup and -s/--jstack (tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerSubCommand.java:53-64), while execute() branches on the option:

if (!commandLine.hasOption('i')) {
    ... // every client of the group
} else {
    String clientId = commandLine.getOptionValue('i').trim();   // :125
    ...
}

Because DefaultParser rejects an option that is not registered, mqadmin consumer -g <group> -i <clientId> aborts with Unrecognized option: -i and System.exit(1) (srvutil/.../ServerUtil.java:54-58); the else branch is therefore unreachable dead code and the per-client query the command was written to support cannot be run at all.

(2) mqadmin resetMasterFlushOffset: -b and -o are declared optional but read unconditionally.

Option opt = new Option("b", "brokerAddr", true, "which broker to reset");
opt.setRequired(false);                     // :41-42
opt = new Option("o", "offset", true, "the offset to reset at");
opt.setRequired(false);                     // :45-46
...
String brokerAddr = commandLine.getOptionValue('b').trim();            // :60
long masterFlushOffset = Long.parseLong(commandLine.getOptionValue('o').trim());   // :61

With either option omitted the parse succeeds and the command fails with a NullPointerException from the .trim() (there is no fallback for either value): mqadmin resetMasterFlushOffset crashes on -b, mqadmin resetMasterFlushOffset -b 127.0.0.1:10911 crashes on -o, and -o with a non-numeric value gives a NumberFormatException. The exception is wrapped into a SubCommandException and printed as a stack trace by MQAdminStartup (tools/.../command/MQAdminStartup.java:194-196), so the user gets no usage information at all.

(3) mqadmin decodeMessageId: -i is declared optional but read unconditionally.

DecodeMessageIdCommond.buildCommandlineOptions marks -i/--messageId setRequired(false) (tools/.../command/message/DecodeMessageIdCommond.java:41-42) and execute() immediately does commandLine.getOptionValue('i').trim() (:50, outside the try), so running the command without -i throws a NullPointerException instead of reporting the missing argument.

Steps to Reproduce
  1. mqadmin consumer -g myGroup -i 172.17.0.1@1234 → Unrecognized option: -i plus the help text, exit code 1, although execute() implements exactly this query.
  2. mqadmin resetMasterFlushOffset (or with only -b, or with only -o) → SubCommandException: ResetMasterFlushOffsetSubCommand command failed caused by java.lang.NullPointerException.
  3. mqadmin decodeMessageId → java.lang.NullPointerException from DecodeMessageIdCommond.execute.

At the unit level each case is reproduced by parsing the command's own option set with a plain DefaultParser (the pattern the module's existing tests use): the invocation either parses or it does not, and the missing-argument behavior is what the user sees before execute() runs.

What Did You Expect to See?
  • consumer -i <clientId> is accepted and performs the single-client query, like the sibling consumerStatus command whose -i/--clientId option is declared.
  • resetMasterFlushOffset without -b/-o and decodeMessageId without -i are rejected by the parser with the standard Missing required option: -x message and the command's help, the way every other mqadmin command whose arguments are mandatory behaves — instead of reaching execute() and dying with a NullPointerException stack trace.
What Did You See Instead?

Either an option the command actually reads is rejected as unknown, or a mandatory argument that is missing produces a raw NullPointerException / NumberFormatException with no usage hint. Both make the tool's own contract unreadable: mqadmin help <command> cannot even tell which arguments are mandatory, because they are declared optional.

Additional Context

Minimal fix: declare the missing option and mark the mandatory ones required —

  • ConsumerSubCommand: add new Option("i", "clientId", true, "The consumer's client id") (optional, as in ConsumerStatusSubCommand);
  • ResetMasterFlushOffsetSubCommand: -b/--brokerAddr and -o/--offset become required;
  • DecodeMessageIdCommond: -i/--messageId becomes required.

Each command keeps its documented usage working (the flags are only made mandatory where execute() has no alternative path). I will open a PR with the changes plus tests that parse each command's own Options (asserting the missing-argument rejection and that the normal invocation still parses).

Dominant language
Java
Stars
22.7k
Forks
12k
Avg merge
3d 18h
Merged PRs (30d)
17

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from apache/rocketmq

All issues in apache/rocketmq

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.