jhy/jsoup · error · IllegalArgumentException

Range positions must be non-negative

Error message

Range positions must be non-negative

What it means

Range represents a source-position span for a node when tracking is enabled. Its private constructor requires both start and end character offsets to be >= 0; negative offsets indicate corrupt position data, so jsoup throws IllegalArgumentException('Range positions must be non-negative').

Solutions

  1. Clamp or validate offsets before creating the range: ensure startPos and endPos are >= 0 and startPos <= endPos
  2. Check the offset calculation producing negative values (e.g. subtracting a larger prefix length)
  3. Enable jsoup's position tracking with Parser.setTrackPosition(true) and rely on jsoup's own offsets rather than hand-built ranges
  4. Catch IllegalArgumentException around range construction when offsets come from untrusted computation

Example fix

// before
Range r = new Range(lineMap, computedStart, computedEnd); // computedStart = -3
// after
if (computedStart < 0) computedStart = 0;
Range r = new Range(lineMap, Math.max(0, computedStart), Math.max(0, computedEnd));
Defensive patterns

Strategy: validation

Validate before calling

boolean validRange(int start, int end) { return start >= 0 && end >= 0 && start <= end; }

Try / catch

try { Range r = makeRange(startPos, endPos); } catch (IllegalArgumentException e) { if (e.getMessage().contains("non-negative")) { /* fix offsets or skip */ } throw e; }

Prevention

When it happens

Trigger: Constructing a Range (directly or via node source position setters / Line-Number tracking) with a negative startPos or endPos, usually from a miscomputed offset in custom parsing or node-movement code.

Common situations: Custom DocumentType/Node position bookkeeping that subtracts offsets incorrectly, integration code copying ranges between documents, or off-by-one/negative results from upstream offset math.

Related errors


AI-assisted analysis of jhy/jsoup@9851ac5d9c (2026-09-08). Data as JSON: /api/errors/73cbe17a18ae5e83. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/org/jsoup/nodes/Range.java:42

    private final int startPos;
    private final int endPos;

    /**
     Creates the untracked source range sentinel.
     */
    private Range() {
        lineMap = UnsetLineMap;
        startPos = -1;
        endPos = -1;
    }

    /**
     Creates a new Range from source offsets.
     */
    private Range(LineMap lineMap, int startPos, int endPos) {
        this.lineMap = lineMap;
        if (startPos < 0 || endPos < 0)
            throw new IllegalArgumentException("Range positions must be non-negative");
        this.startPos = startPos;
        this.endPos = endPos;
    }

    /**
     Get the start position of this range, with 1-based line and column coordinates.
     * @return the start position.
     */
    public Position start() {
        return startPos == -1 ? UntrackedPos : position(startPos);
    }

    /**
     Get the starting source offset of this range.
     @return the 0-based start source offset.
     @since 1.17.1
     */
    public int startPos() {

View on GitHub (pinned to 9851ac5d9c)