{"record":{"id":"18145310a8137643","repo":"oracle/graal","slug":"empty-cursor-does-not-have-elements","errorCode":null,"errorMessage":"Empty cursor does not have elements","messagePattern":"Empty cursor does not have elements","errorType":"exception","errorClass":"NoSuchElementException","httpStatus":null,"severity":"error","filePath":"sdk/src/org.graalvm.collections/src/org/graalvm/collections/EmptyMap.java","lineNumber":55,"sourceCode":" * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\npackage org.graalvm.collections;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\nimport java.util.function.BiFunction;\n\n/**\n * Singleton instances for empty maps and the corresponding iterators and cursors.\n */\nclass EmptyMap {\n\n    static final MapCursor<Object, Object> EMPTY_CURSOR = new MapCursor<>() {\n        @Override\n        public void remove() {\n            throw new NoSuchElementException(\"Empty cursor does not have elements\");\n        }\n\n        @Override\n        public boolean advance() {\n            return false;\n        }\n\n        @Override\n        public Object getKey() {\n            throw new NoSuchElementException(\"Empty cursor does not have elements\");\n        }\n\n        @Override\n        public Object getValue() {\n            throw new NoSuchElementException(\"Empty cursor does not have elements\");\n        }\n\n        @Override","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/sdk/src/org.graalvm.collections/src/org/graalvm/collections/EmptyMap.java#L37-L73","documentation":"MapCursor.remove() on the shared EMPTY_CURSOR singleton (returned by EconomicMap.getEntries() for an empty map) always throws NoSuchElementException. An empty cursor is positioned on no element, so there is nothing to remove; the library treats calling a cursor method that requires a current element as an iteration error, mirroring java.util.Iterator semantics.","triggerScenarios":"Obtaining a cursor from an empty EconomicMap via map.getEntries() and calling remove() without a preceding successful advance(). Note the cursor starts 'before' the first element; remove() is only valid after advance() returned true. Also triggered by calling remove() twice after one advance().","commonSituations":"Generic iteration code written for java.util.Map.entrySet().iterator() ported to MapCursor; cleanup loops that call remove() unconditionally per iteration; cursors obtained from EconomicMaps.createMap() that is later emptied by clear() before iteration.","solutions":["Only call cursor.remove() after cursor.advance() has returned true, exactly once per element.","Check map.isEmpty() (or !cursor.advance()) before performing cursor operations.","Prefer map.removeKey(key) over cursor-based removal when you already know the key."],"exampleFix":"// before\nMapCursor<K,V> c = map.getEntries();\nwhile (c.advance()) { ... }\nc.remove(); // wrong: cursor exhausted / empty\n\n// after\nMapCursor<K,V> c = map.getEntries();\nwhile (c.advance()) {\n    if (shouldDrop(c.getKey())) {\n        c.remove(); // valid: current element exists\n    }\n}","handlingStrategy":"validation","validationCode":"if (!map.isEmpty()) {\n    MapCursor<K,V> c = map.getEntries();\n    while (c.advance()) {\n        if (drop(c.getKey())) c.remove(); // remove only while positioned\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat MapCursor like Iterator: mutate only between a true advance() and the next advance().","Prefer map.removeKey(key) when the key is already known.","Never call cursor methods after the loop exits."],"tags":["graalvm","collections","iteration","cursor"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}