microg/GmsCore · error · IllegalArgumentException

maxUpdateAgeMillis must be greater than or equal to 0, or IM

Error message

maxUpdateAgeMillis must be greater than or equal to 0, or IMPLICIT_MAX_UPDATE_AGE

What it means

setMaxUpdateAgeMillis accepts any non-negative age, plus the special sentinel IMPLICIT_MAX_UPDATE_AGE (-1) meaning 'no explicit max age'. Any other negative value is rejected with IllegalArgumentException. This keeps the sentinel convention intact while blocking meaningless negative durations.

Source

Thrown at play-services-location/src/main/java/com/google/android/gms/location/LocationRequest.java:723

            if (intervalMillis < 0) throw new IllegalArgumentException("intervalMillis must be greater than or equal to 0");
            this.intervalMillis = intervalMillis;
            return this;
        }

        /**
         * Sets the maximum age of an initial historical location delivered for this request. A value of 0 indicates that no initial
         * historical location will be delivered, only freshly derived locations. A value {@link Long#MAX_VALUE} represents an effectively
         * unbounded maximum age.
         * <p>
         * This may be set to the special value {@link #IMPLICIT_MAX_UPDATE_AGE} in which case the maximum update age will always be
         * the same as the interval.
         * <p>
         * The default value is {@link #IMPLICIT_MAX_UPDATE_AGE}.
         */
        @NonNull
        public Builder setMaxUpdateAgeMillis(long maxUpdateAgeMillis) {
            if (maxUpdateAgeMillis < 0 && maxUpdateAgeMillis != IMPLICIT_MAX_UPDATE_AGE)
                throw new IllegalArgumentException("maxUpdateAgeMillis must be greater than or equal to 0, or IMPLICIT_MAX_UPDATE_AGE");
            this.maxUpdateAgeMillis = maxUpdateAgeMillis;
            return this;
        }

        /**
         * Sets the longest a location update may be delayed. This parameter controls location batching behavior. If this is set to a
         * value at least 2x larger than the interval specified by {@link #setIntervalMillis(long)}, then a device may (but is not required
         * to) save power by delivering locations in batches. If clients do not require immediate delivery, consider setting this value
         * as high as is reasonable to allow for additional power savings. When the {@link LocationRequest} is built, the maximum
         * update delay will be set to the max of the provided maximum update delay and the interval. This normalizes requests
         * without batching to have the maximum update delay equal to the interval.
         * <p>
         * For example, if a request is made with a 2s interval and a 10s maximum update delay, this implies that the device may
         * choose to deliver batches of 5 locations every 10s (where each location in a batch represents a point in time ~2s after
         * the previous).
         * <p>
         * Support for batching may vary by device hardware, so simply allowing batching via this parameter does not imply a client
         * will receive batched results on all devices.

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Use the documented sentinel: builder.setMaxUpdateAgeMillis(LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE) to reset to the implicit default.
  2. Pass a non-negative age in milliseconds for an explicit maximum age, e.g. setMaxUpdateAgeMillis(10_000).
  3. Clamp or validate computed values: if (age < 0 && age != IMPLICIT_MAX_UPDATE_AGE) age = IMPLICIT_MAX_UPDATE_AGE;

Example fix

// before
builder.setMaxUpdateAgeMillis(-1); // not the sentinel constant path
// after
builder.setMaxUpdateAgeMillis(LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE); // or a value >= 0
Defensive patterns

Strategy: validation

Validate before calling

private static final long RESET_MAX_AGE = LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE;
public static boolean isValidMaxUpdateAge(long ageMillis) {
    return ageMillis >= 0 || ageMillis == LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE;
}

Type guard

static boolean isValidMaxUpdateAge(long v) { return v >= 0 || v == IMPLICIT_MAX_UPDATE_AGE; }

Try / catch

try {
    return builder.setMaxUpdateAgeMillis(age);
} catch (IllegalArgumentException e) {
    if (!e.getMessage().contains("maxUpdateAgeMillis")) throw e;
    return builder.setMaxUpdateAgeMillis(IMPLICIT_MAX_UPDATE_AGE); // sentinel fallback
}

Prevention

When it happens

Trigger: Calling builder.setMaxUpdateAgeMillis(-1) expecting the default without using the IMPLICIT_MAX_UPDATE_AGE constant, or passing any negative long such as setMaxUpdateAgeMillis(-5000) from a computed or config-sourced value.

Common situations: Developers hard-coding -1 as a 'reset to default' value instead of referencing LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE; parsing settings where -1 and other negatives were not distinguished; unit conversion introducing a negative sign.

Related errors


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