jhy/jsoup · error · IllegalArgumentException

Attribute range positions must be non-negative

Error message

Attribute range positions must be non-negative

What it means

Range.AttributeRange records source offsets for an attribute's name and value. Its private constructor validates all four offsets; any negative value throws IllegalArgumentException('Attribute range positions must be non-negative').

Solutions

  1. Validate all four offsets are >= 0 before constructing; clamp negatives to 0 or skip assigning a range
  2. Fix the offset computation returning -1 (e.g. indexOf miss) upstream
  3. Use ranges produced by jsoup's own position tracking (Parser.setTrackPosition(true)) instead of manual values
  4. Wrap construction in try-catch and degrade to an unpositioned attribute on failure

Example fix

// before
attr.attributeRange(lineMap, nameStart, nameEnd, valStart, valEnd); // nameStart = -1
// after
if (nameStart >= 0 && nameEnd >= 0 && valStart >= 0 && valEnd >= 0)
    attr.attributeRange(lineMap, nameStart, nameEnd, valStart, valEnd);
Defensive patterns

Strategy: validation

Validate before calling

boolean validAttrRange(int ns, int ne, int vs, int ve) { return ns >= 0 && ne >= 0 && vs >= 0 && ve >= 0; }

Try / catch

try { attr.attributeRange(lineMap, ns, ne, vs, ve); } catch (IllegalArgumentException e) { if (e.getMessage().contains("Attribute range")) { /* skip or default positions */ } throw e; }

Prevention

When it happens

Trigger: Constructing an AttributeRange with a negative nameStartPos, nameEndPos, valueStartPos, or valueEndPos — typically from custom attribute-position bookkeeping or copying positions from another document.

Common situations: Custom parsers or transformer code that assigns attribute ranges, offset math that yields -1 when a token wasn't found, or mixing ranges across documents parsed without position tracking.

Related errors


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

Appendix: source

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

        /**
         Creates the untracked attribute source range sentinel.
         */
        private AttributeRange() {
            lineMap         = UnsetLineMap;
            nameStartPos    = -1;
            nameEndPos      = -1;
            valueStartPos   = -1;
            valueEndPos     = -1;
        }

        /**
         Creates a new AttributeRange from source offsets.
         */
        private AttributeRange(LineMap lineMap, int nameStartPos, int nameEndPos, int valueStartPos, int valueEndPos) {
            this.lineMap = lineMap;
            if (nameStartPos < 0 || nameEndPos < 0 || valueStartPos < 0 || valueEndPos < 0)
                throw new IllegalArgumentException("Attribute range positions must be non-negative");
            this.nameStartPos = nameStartPos;
            this.nameEndPos = nameEndPos;
            this.valueStartPos = valueStartPos;
            this.valueEndPos = valueEndPos;
        }

        /** Get the source range for the attribute's name. */
        public Range nameRange() {
            return isTracked() ? new Range(lineMap, nameStartPos, nameEndPos) : Range.Untracked;
        }

        /** Get the source range for the attribute's value. */
        public Range valueRange() {
            return isTracked() ? new Range(lineMap, valueStartPos, valueEndPos) : Range.Untracked;
        }

        /**
         Tests if this attribute range has tracked name and value offsets.

View on GitHub (pinned to 9851ac5d9c)