{"record":{"id":"76deef911a0e2a12","repo":"apache/pulsar","slug":"positions-must-not-be-null","errorCode":null,"errorMessage":"Positions must not be null","messagePattern":"Positions must not be null","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java","lineNumber":4020,"sourceCode":"            .whenComplete((result, exception) -> {\n                    if (exception == null) {\n                        log.info().attr(\"ledgerId\", ledgerId).attr(\"uuid\", uuid).log(\"End Offload\");\n                    } else {\n                        log.warn().attr(\"ledgerId\", ledgerId)\n                                .attr(\"uuid\", uuid)\n                                .exception(exception).log(\"Failed to complete offload\");\n                    }\n                });\n    }\n\n    /**\n     * Compare two positions. It is different with {@link Position#compareTo(Position)} when the params are invalid.\n     * For example: position-1 is \"1:{latest entry}\", and position-2 is \"2:-1\", they are the same position.\n     */\n    @VisibleForTesting\n    int comparePositions(Position pos1, Position pos2) {\n        if (pos1 == null || pos2 == null) {\n            throw new IllegalArgumentException(\"Positions must not be null\");\n        }\n        if (ledgers.isEmpty() || pos1.getLedgerId() < getFirstPosition().getLedgerId()\n                || pos2.getLedgerId() < getFirstPosition().getLedgerId()\n                || pos1.getLedgerId() > getLastPosition().getLedgerId()\n                || pos2.getLedgerId() > getLastPosition().getLedgerId()) {\n            return pos1.compareTo(pos2);\n        }\n        if (pos1.getLedgerId() == pos2.getLedgerId()) {\n            return Long.compare(pos1.getEntryId(), pos2.getEntryId());\n        }\n        if (!isValidPosition(pos1) || !isValidPosition(pos2)) {\n            return getNextValidPosition(pos1).compareTo(getNextValidPosition(pos2));\n        }\n        return pos1.compareTo(pos2);\n    }\n\n    /**\n     * Get the number of entries between a contiguous range of two positions.","sourceCodeStart":4002,"sourceCodeEnd":4038,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedLedgerImpl.java#L4002-L4038","documentation":"ManagedLedgerImpl.comparePositions() is a null-strict position comparator used by the entries-count/range APIs. It throws IllegalArgumentException when either Position argument is null. Unlike Position.compareTo, it validates inputs before comparing, since null positions would NPE deeper in the logic anyway.","triggerScenarios":"Calling getNumberOfEntries(Range<Position>) or related range APIs with a null from/to position — typically a cursor with no mark-delete position yet, or caller code passing an uninitialized Position.","commonSituations":"Newly created subscription whose read position has never been initialized; application code computing stats ranges before a first consume; deserialization producing null positions.","solutions":["Null-check positions before invoking the range API and skip/return 0 entries for null input","For a fresh cursor, initialize or wait until markDeletePosition/readPosition is established","Fix caller code that produces null Positions (uninitialized fields, failed deserialization)"],"exampleFix":"// before\nlong n = ml.getNumberOfEntries(Range.open(fromPos, toPos)); // NPE risk\n// after\nlong n = (fromPos == null || toPos == null) ? 0\n        : ml.getNumberOfEntries(Range.open(fromPos, toPos));","handlingStrategy":"validation","validationCode":"if (fromPos == null || toPos == null) {\n    return 0L; // or skip the computation\n}","typeGuard":"static boolean validPositions(Position a, Position b) {\n    return a != null && b != null;\n}","tryCatchPattern":"try {\n    return ml.getNumberOfEntries(range);\n} catch (IllegalArgumentException e) {\n    log.warn(\"null/invalid positions for entry count\", e);\n    return 0L;\n}","preventionTips":["Null-check positions before range APIs","Handle fresh cursors with uninitialized positions","Treat null positions as 'no entries' rather than propagating","Validate deserialized Position objects"],"tags":["null-check","illegal-argument","positions","managed-ledger"],"backgroundTag":"null-argument","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}