microg/GmsCore · error · IOException
ERROR_BACKOFF
ERROR_BACKOFF
Error message
ERROR_BACKOFF
What it means
InstanceIdRpc.sendRegisterMessage throws ERROR_BACKOFF when the device is still within an exponential-backoff window from a previous failed registration attempt. The RPC tracks nextAttempt (based on SystemClock.elapsedRealtime()) and refuses to send new register messages until that time passes, to avoid hammering the registration service.
Solutions
- Wait until the backoff interval elapses before retrying — schedule the retry with a delay rather than calling immediately
- Implement exponential backoff (start ~500ms, double on each failure, cap e.g. 60s) in the retry wrapper
- Clear app data / reinstall only as a last resort to reset in-memory backoff state; usually just retrying later suffices
Example fix
// before
while (true) { try { return getToken(); } catch (IOException e) { continue; } }
// after
long backoffMs = 500;
for (int i = 0; i < MAX_RETRIES; i++) {
try { return getToken(); }
catch (IOException e) {
SystemClock.sleep(backoffMs);
backoffMs = Math.min(backoffMs * 2, 60000);
}
} Defensive patterns
Strategy: retry
Validate before calling
// cannot query nextAttempt; wrap with time-since-last-failure tracking long sinceLastFail = SystemClock.elapsedRealtime() - lastFailureAt; if (lastFailureAt != 0 && sinceLastFail < 5000) return; // still cooling down
Try / catch
try {
String token = instanceId.getToken(senderId, "GCM");
} catch (IOException e) {
if ("ERROR_BACKOFF".equals(e.getMessage())) {
handler.postDelayed(this::retry, currentBackoffMs());
}
} Prevention
- Always retry with exponential backoff, never in a tight loop
- Persist failure timestamps so restarts don't immediately re-hit the backoff
- Only re-request tokens when actually invalidated or missing
When it happens
Trigger: Calling getToken or sendRegisterMessageBlockingInternal while nextAttempt != 0 and elapsedRealtime <= nextAttempt, i.e. retrying token registration before the backoff interval computed after earlier failures has expired.
Common situations: Tight retry loops around getToken after a transient network failure or service-not-available response; an app restarting frequently and re-requesting tokens before the backoff window elapses; retry logic that ignores the recommended exponential backoff schedule.
Related errors
- ERROR_SERVICE_NOT_AVAILABLE
- ERROR_TIMEOUT
- (String) res
- A path must start with a single / .
- Access denied, missing google package permission for
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/94d75d95a9d3618a.
Report an issue: GitHub.
Appendix: source
Thrown at play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdRpc.java:275
private synchronized PendingIntent getSelfAuthToken() {
if (selfAuthToken == null) {
Intent intent = new Intent();
intent.setPackage("com.google.example.invalidpackage");
selfAuthToken = PendingIntent.getBroadcast(context, 0, intent, 0);
}
return selfAuthToken;
}
private static synchronized String getRequestId() {
return Integer.toString(lastRequestId++);
}
private void sendRegisterMessage(Bundle data, KeyPair keyPair, String requestId) throws IOException {
long elapsedRealtime = SystemClock.elapsedRealtime();
if (nextAttempt != 0 && elapsedRealtime <= nextAttempt) {
Log.w(TAG, "Had to wait for " + interval + ", that's still " + (nextAttempt - elapsedRealtime));
throw new IOException(ERROR_BACKOFF);
}
initialize();
if (iidPackageName == null) {
throw new IOException(ERROR_MISSING_INSTANCEID_SERVICE);
}
Intent intent = new Intent(ACTION_C2DM_REGISTER);
intent.setPackage(iidPackageName);
data.putString(EXTRA_GMS_VERSION, Integer.toString(getGmsVersionCode(context)));
data.putString(EXTRA_OS_VERSION, Integer.toString(SDK_INT));
data.putString(EXTRA_APP_VERSION_CODE, Integer.toString(getSelfVersionCode(context)));
data.putString(EXTRA_APP_VERSION_NAME, getSelfVersionName(context));
data.putString(EXTRA_CLIENT_VERSION, "iid-" + GMS_VERSION_CODE);
data.putString(EXTRA_APP_ID, InstanceID.sha1KeyPair(keyPair));
String pub = base64encode(keyPair.getPublic().getEncoded());
data.putString(EXTRA_PUBLIC_KEY, pub);
data.putString(EXTRA_SIGNATURE, sign(keyPair, context.getPackageName(), pub));
intent.putExtras(data);
intent.putExtra(EXTRA_APP, getSelfAuthToken());View on GitHub (pinned to 157c9d86ac)