Skip to content

Support fallback/default value for options specified without a value #446

@stalep

Description

@stalep

Summary

When a CLI option is specified without a value (e.g. --debug instead of --debug=5005), there is no built-in way to assign a fallback value. This is a three-state problem that neither defaultValue nor optionalValue solves:

Invocation Desired debug value What the user means
app run test.java null (no debugging) "I don't want debugging"
app run --debug test.java "4004" (default port) "Enable debugging with sensible defaults"
app run --debug=5005 test.java "5005" (explicit port) "Enable debugging on this specific port"
  • defaultValue cannot distinguish row 1 from row 2 -- it applies to both "not specified" and would incorrectly enable debugging when the user didn't ask for it.
  • optionalValue allows bare --debug but leaves the value as null, so the command cannot distinguish "debugging requested with defaults" from "not requested at all" without a custom parser.

Current workaround

This requires a custom OptionParser implementation + post-parse resolution:

// 1. Custom parser that sets "" sentinel when no value is given
public class StrictOptionParser implements OptionParser {
    @Override
    public void parse(ParsedLineIterator iter, ProcessedOption option) {
        String word = iter.peekWord();
        String fp = "--" + option.name();
        if (word.startsWith(fp + "=")) {
            option.addValue(word.substring(fp.length() + 1));
        } else {
            option.addValue("");  // sentinel for "present without value"
        }
        iter.pollParsedWord();
    }
}

// 2. Option declaration with custom parser
@Option(name = "debug", parser = StrictOptionParser.class)
public String debugRaw;

// 3. Manual post-parse resolution in afterParse()
public void resolveAfterParse() {
    if ("".equals(debugRaw)) {
        // Option was present without value -- use fallback
        debugRaw = "4004";
    }
    // debugRaw == null means option was not specified at all
}

This pattern is repeated for every option that needs it (--debug, --jfr, --module, --code).

Proposed enhancement

Add a fallbackValue attribute to @Option:

@Option(name = "debug", fallbackValue = "4004",
        description = "Enable debugging. Default port: ${FALLBACK-VALUE}")
public String debug;

Behavior:

  • Not specifieddebug = null (or DefaultValueProvider value if configured)
  • --debug (bare) → debug = "4004" (fallback value applied)
  • --debug=5005 (explicit) → debug = "5005"

This eliminates the need for custom OptionParser implementations and manual post-parse resolution.

Why not defaultValue?

defaultValue = "4004" would make debug = "4004" even when the user never typed --debug, which is wrong -- it would silently enable debugging on every invocation.

Why not optionalValue?

optionalValue = true allows bare --debug but sets the value to null, making it indistinguishable from "option not specified at all". The command needs to know whether the user explicitly requested the feature.

Context

In jbang (jbangdev/jbang#2453), this pattern is used for --debug, --jfr, --module, and --code. Each requires a custom OptionParser + post-parse resolution, adding significant boilerplate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions