{"record":{"id":"4452196287918da0","repo":"microg/GmsCore","slug":"maxupdateagemillis-must-be-greater-than-or-equal-t-445219","errorCode":null,"errorMessage":"maxUpdateAgeMillis must be greater than or equal to 0, or IMPLICIT_MAX_UPDATE_AGE","messagePattern":"maxUpdateAgeMillis must be greater than or equal to 0, or IMPLICIT_MAX_UPDATE_AGE","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"play-services-location/src/main/java/com/google/android/gms/location/LocationRequest.java","lineNumber":723,"sourceCode":"            if (intervalMillis < 0) throw new IllegalArgumentException(\"intervalMillis must be greater than or equal to 0\");\n            this.intervalMillis = intervalMillis;\n            return this;\n        }\n\n        /**\n         * Sets the maximum age of an initial historical location delivered for this request. A value of 0 indicates that no initial\n         * historical location will be delivered, only freshly derived locations. A value {@link Long#MAX_VALUE} represents an effectively\n         * unbounded maximum age.\n         * <p>\n         * This may be set to the special value {@link #IMPLICIT_MAX_UPDATE_AGE} in which case the maximum update age will always be\n         * the same as the interval.\n         * <p>\n         * The default value is {@link #IMPLICIT_MAX_UPDATE_AGE}.\n         */\n        @NonNull\n        public Builder setMaxUpdateAgeMillis(long maxUpdateAgeMillis) {\n            if (maxUpdateAgeMillis < 0 && maxUpdateAgeMillis != IMPLICIT_MAX_UPDATE_AGE)\n                throw new IllegalArgumentException(\"maxUpdateAgeMillis must be greater than or equal to 0, or IMPLICIT_MAX_UPDATE_AGE\");\n            this.maxUpdateAgeMillis = maxUpdateAgeMillis;\n            return this;\n        }\n\n        /**\n         * Sets the longest a location update may be delayed. This parameter controls location batching behavior. If this is set to a\n         * value at least 2x larger than the interval specified by {@link #setIntervalMillis(long)}, then a device may (but is not required\n         * to) save power by delivering locations in batches. If clients do not require immediate delivery, consider setting this value\n         * as high as is reasonable to allow for additional power savings. When the {@link LocationRequest} is built, the maximum\n         * update delay will be set to the max of the provided maximum update delay and the interval. This normalizes requests\n         * without batching to have the maximum update delay equal to the interval.\n         * <p>\n         * For example, if a request is made with a 2s interval and a 10s maximum update delay, this implies that the device may\n         * choose to deliver batches of 5 locations every 10s (where each location in a batch represents a point in time ~2s after\n         * the previous).\n         * <p>\n         * Support for batching may vary by device hardware, so simply allowing batching via this parameter does not imply a client\n         * will receive batched results on all devices.","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/src/main/java/com/google/android/gms/location/LocationRequest.java#L705-L741","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use the documented sentinel: builder.setMaxUpdateAgeMillis(LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE) to reset to the implicit default.","Pass a non-negative age in milliseconds for an explicit maximum age, e.g. setMaxUpdateAgeMillis(10_000).","Clamp or validate computed values: if (age < 0 && age != IMPLICIT_MAX_UPDATE_AGE) age = IMPLICIT_MAX_UPDATE_AGE;"],"exampleFix":"// before\nbuilder.setMaxUpdateAgeMillis(-1); // not the sentinel constant path\n// after\nbuilder.setMaxUpdateAgeMillis(LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE); // or a value >= 0","handlingStrategy":"validation","validationCode":"private static final long RESET_MAX_AGE = LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE;\npublic static boolean isValidMaxUpdateAge(long ageMillis) {\n    return ageMillis >= 0 || ageMillis == LocationRequest.Builder.IMPLICIT_MAX_UPDATE_AGE;\n}","typeGuard":"static boolean isValidMaxUpdateAge(long v) { return v >= 0 || v == IMPLICIT_MAX_UPDATE_AGE; }","tryCatchPattern":"try {\n    return builder.setMaxUpdateAgeMillis(age);\n} catch (IllegalArgumentException e) {\n    if (!e.getMessage().contains(\"maxUpdateAgeMillis\")) throw e;\n    return builder.setMaxUpdateAgeMillis(IMPLICIT_MAX_UPDATE_AGE); // sentinel fallback\n}","preventionTips":["Always reference IMPLICIT_MAX_UPDATE_AGE instead of hard-coding -1 to mean 'default'.","Treat -1 as a reserved sentinel: reject all other negatives in your own config layer.","Document in your settings schema that maxUpdateAge accepts [0, +inf) or the sentinel."],"tags":["android","location","illegal-argument","validation"],"backgroundTag":"argument-out-of-range","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}