HMCL-dev/HMCL · error · ServerResponseMalformedException

Selected profile changed

Error message

Selected profile changed

What it means

YggdrasilAccount.logIn throws ServerResponseMalformedException("Selected profile changed") when the refreshed/validated session's selected profile UUID differs from the account's stored profileID. HMCL treats this as a tampered or inconsistent server response, since the server must return the same character the account is bound to.

Solutions

  1. Remove the account from the launcher and re-add it (full login) so it re-binds to the current character.
  2. Select a different character via the account's character-selection flow if the server offers availableProfiles.
  3. Verify you are authenticating against the same yggdrasil server the account was created on.

Example fix

// before
account.logIn(); // throws: stored profileID no longer matches server
// after
if (account.getProfileId() != null) {
    try { account.logIn(); }
    catch (ServerResponseMalformedException e) {
        account.remove();
        account = factory.create(server, username, password);
        account.logIn();
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (account.getProfileId() != null && !sessionHasProfileWithId(account.getProfileId())) {
    // profile changed server-side: force full re-login instead of logIn()
}

Try / catch

try {
    account.logIn();
} catch (ServerResponseMalformedException e) {
    // remove account and re-add / re-select a character
    account.remove();
    account = factory.create(...);
}

Prevention

When it happens

Trigger: Calling logIn() (which performs token refresh/validate) after the account's selected character was deleted, replaced, or changed server-side so the returned selectedProfile id no longer equals the stored profileID.

Common situations: Character deleted on the auth server while the launcher still references it; switching auth servers/authlib-injector backends that reuse tokens differently; duplicated account entries after migration.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java:122

    @Override
    public synchronized AuthInfo logIn() throws AuthenticationException {
        if (!authenticated || !session.hasProfileName()) {
            if (session.hasProfileName() && service.validate(session.getAccessToken(), session.getClientToken())) {
                authenticated = true;
            } else {
                YggdrasilSession acquiredSession;
                try {
                    acquiredSession = service.refresh(session.getAccessToken(), session.getClientToken(), null);
                } catch (RemoteAuthenticationException e) {
                    if ("ForbiddenOperationException".equals(e.getRemoteName())) {
                        throw new CredentialExpiredException(e);
                    } else {
                        throw e;
                    }
                }
                if (acquiredSession.getSelectedProfile() == null ||
                        !acquiredSession.getSelectedProfile().getId().equals(profileID)) {
                    throw new ServerResponseMalformedException("Selected profile changed");
                }
                if (!acquiredSession.hasProfileName()) {
                    throw new ServerResponseMalformedException("Profile name is missing");
                }

                session = acquiredSession;

                authenticated = true;
                invalidate();
            }
        }

        return session.toAuthInfo();
    }

    @Override
    public synchronized AuthInfo logInWithPassword(String password) throws AuthenticationException {
        YggdrasilSession acquiredSession = service.authenticate(loginName, password, randomClientToken());

View on GitHub (pinned to 24702dc5a0)