microg/GmsCore · error · IOException

(String) res

Error message

(String) res

What it means

In sendRegisterMessageBlockingInternal, when the blocking response for a requestId is a plain String instead of an Intent, the library treats that string as an error message and rethrows it inside an IOException. The message here is the raw error string sent back by the registration service, e.g. ERROR_SERVICE_NOT_AVAILABLE or a service-specific code.

Source

Thrown at play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdRpc.java:387

        return intent;
    }

    private Intent sendRegisterMessageBlockingInternal(Bundle data, KeyPair keyPair) throws IOException {
        ConditionVariable cv = new ConditionVariable();
        String requestId = getRequestId();
        synchronized (InstanceIdRpc.class) {
            blockingResponses.put(requestId, cv);
        }

        sendRegisterMessage(data, keyPair, requestId);

        cv.block(BLOCKING_WAIT_TIME);
        synchronized (InstanceIdRpc.class) {
            Object res = blockingResponses.remove(requestId);
            if (res instanceof Intent) {
                return (Intent) res;
            } else if (res instanceof String) {
                throw new IOException((String) res);
            }
            Log.w(TAG, "No response " + res);
            throw new IOException(ERROR_TIMEOUT);
        }
    }

    public String handleRegisterMessageResult(Intent resultIntent) throws IOException {
        if (resultIntent == null) throw new IOException(ERROR_SERVICE_NOT_AVAILABLE);
        String result = resultIntent.getStringExtra(EXTRA_REGISTRATION_ID);
        if (result == null) result = resultIntent.getStringExtra(EXTRA_UNREGISTERED);
        if (result != null) return result;
        result = resultIntent.getStringExtra(EXTRA_ERROR);
        throw new IOException(result != null ? result : ERROR_SERVICE_NOT_AVAILABLE);
    }

    private void setResponse(String requestId, Object response) {
        if (requestId == null) {
            for (String r : blockingResponses.keySet()) {

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Catch the IOException and inspect getMessage() to branch on known codes (ERROR_SERVICE_NOT_AVAILABLE, ERROR_BACKOFF, etc.)
  2. Retry with exponential backoff when the message is ERROR_SERVICE_NOT_AVAILABLE
  3. Check network connectivity and Google Play Services availability before calling getToken
  4. Log the raw message — a non-standard string often indicates a provider-side (GMS/microG) problem

Example fix

// before
String token = instanceId.getToken(senderId, "GCM");
// after
try {
    String token = instanceId.getToken(senderId, "GCM");
} catch (IOException e) {
    if ("ERROR_SERVICE_NOT_AVAILABLE".equals(e.getMessage())) {
        scheduleRetryWithBackoff();
    } else {
        Log.w(TAG, "Registration failed: " + e.getMessage());
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure connectivity before registration
if (!isNetworkAvailable(context)) deferRegistration();

Try / catch

try {
    String token = instanceId.getToken(senderId, "GCM");
} catch (IOException e) {
    String msg = e.getMessage();
    if (msg != null && msg.startsWith("ERROR_")) {
        handleKnownIidError(msg); // branch per known code, retry if transient
    } else {
        logAndReport(msg); // provider-specific error
    }
}

Prevention

When it happens

Trigger: The IID service (or microG's RPC receiver) delivered an error result via setResponse(requestId, errorMessage) for the pending request; the blocked thread wakes with a String payload and throws IOException(errorMessage).

Common situations: Server-side registration failures (quota, invalid sender); device connectivity problems mid-registration; Play Services returning structured error strings instead of result Intents; microG implementations mapping GMS errors to string responses.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/51422e6b25504230. Report an issue: GitHub.