HMCL-dev/HMCL · error · UnsupportedOperationException

grant flow

Error message

grant flow 

What it means

OAuth.authenticate switches on the configured grantFlow. Only AUTHORIZATION_CODE and DEVICE are implemented; any other GrantFlow value reaches the default branch and throws UnsupportedOperationException. It marks an unimplemented enum value, typically from a custom or newly added flow.

Solutions

  1. Use GrantFlow.AUTHORIZATION_CODE or GrantFlow.DEVICE
  2. If you added a new GrantFlow value, implement its branch in OAuth.authenticate
  3. Log/inspect the configured flow value to confirm what is being passed

Example fix

// before
new OAuth(GrantFlow.PASSWORD, options).authenticate();
// after
new OAuth(GrantFlow.AUTHORIZATION_CODE, options).authenticate();
Defensive patterns

Strategy: validation

Validate before calling

if (grantFlow != GrantFlow.AUTHORIZATION_CODE && grantFlow != GrantFlow.DEVICE) {
    throw new IllegalArgumentException("Unsupported grant flow: " + grantFlow);
}

Type guard

boolean supported = grantFlow == GrantFlow.AUTHORIZATION_CODE || grantFlow == GrantFlow.DEVICE;

Try / catch

try {
    return oauth.authenticate();
} catch (UnsupportedOperationException e) {
    log.error("Grant flow not implemented: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Constructing an OAuth authenticator with a grantFlow other than AUTHORIZATION_CODE or DEVICE (e.g. CLIENT_CREDENTIALS or a custom enum constant) and calling authenticate.

Common situations: Adding a new GrantFlow enum constant without wiring it into authenticate; copy-pasted configuration selecting an unsupported flow.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/a420657d1653220d. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java:65

    private final String deviceCodeURL;
    private final String tokenURL;

    public OAuth(String authorizationURL, String accessTokenURL, String deviceCodeURL, String tokenURL) {
        this.authorizationURL = authorizationURL;
        this.accessTokenURL = accessTokenURL;
        this.deviceCodeURL = deviceCodeURL;
        this.tokenURL = tokenURL;
    }

    public Result authenticate(GrantFlow grantFlow, Options options) throws AuthenticationException {
        try {
            switch (grantFlow) {
                case AUTHORIZATION_CODE:
                    return authenticateAuthorizationCode(options);
                case DEVICE:
                    return authenticateDevice(options);
                default:
                    throw new UnsupportedOperationException("grant flow " + grantFlow);
            }
        } catch (IOException e) {
            throw new ServerDisconnectException(e);
        } catch (InterruptedException e) {
            throw new NoSelectedCharacterException();
        } catch (ExecutionException e) {
            if (e.getCause() instanceof InterruptedException) {
                throw new NoSelectedCharacterException();
            } else {
                throw new ServerDisconnectException(e);
            }
        } catch (JsonParseException e) {
            throw new ServerResponseMalformedException(e);
        }
    }

    private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException {
        try (Session session = options.callback.startServer()) {

View on GitHub (pinned to 24702dc5a0)