elastic/elasticsearch · error · IllegalArgumentException

Invalid base cell looking for neighbor

Error message

Invalid base cell looking for neighbor

What it means

Thrown by HexRing.h3NeighborInDirection when the base-cell field of the origin index is outside [0, NUM_BASE_CELLS). h3NeighborInDirection uses the base cell to index baseCellNeighbors/baseCellNeighbor60CCWRots, so an out-of-range base cell would cause an ArrayIndexOutOfBoundsException; this guard throws a clear error instead. The method is package-private and reached via hexRingPosToH3, child/noChild pos methods, and areNeighbours' fallback.

Source

Thrown at libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java:680

    /**
     * Returns the hexagon index neighboring the origin, in the direction dir.
     *
     * Implementation note: The only reachable case where this returns -1 is if the
     * origin is a pentagon and the translation is in the k direction. Thus,
     * -1 can only be returned if origin is a pentagon.
     *
     * @param origin Origin index
     * @param dir Direction to move in
     * @return H3Index of the specified neighbor or -1 if there is no more neighbor
     */
    static long h3NeighborInDirection(long origin, int dir) {
        long current = origin;

        int newRotations = 0;
        int oldBaseCell = H3Index.H3_get_base_cell(current);
        if (oldBaseCell < 0 || oldBaseCell >= Constants.NUM_BASE_CELLS) {  // LCOV_EXCL_BR_LINE
            // Base cells less than zero can not be represented in an index
            throw new IllegalArgumentException("Invalid base cell looking for neighbor");
        }
        int oldLeadingDigit = H3Index.h3LeadingNonZeroDigit(current);

        // Adjust the indexing digits and, if needed, the base cell.
        int r = H3Index.H3_get_resolution(current) - 1;
        while (true) {
            if (r == -1) {
                current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][dir]);
                newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir];

                if (H3Index.H3_get_base_cell(current) == INVALID_BASE_CELL) {
                    // Adjust for the deleted k vertex at the base cell level.
                    // This edge actually borders a different neighbor.
                    current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()]);
                    newRotations = baseCellNeighbor60CCWRots[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()];

                    // perform the adjustment for the k-subsequence we're skipping
                    // over.

View on GitHub (pinned to db6a809a66)

Solutions

  1. Validate with H3.h3IsValid(h3) before any neighbor/ring/child operation.
  2. Re-derive indexes from lat/lng via geoToH3 rather than trusting external longs.
  3. If you must accept raw longs, treat h3IsValid == false as a hard error at the trust boundary.

Example fix

// before
long[] ring = H3.hexRing(rawLong); // throws Invalid base cell if base-cell field invalid

// after
if (H3.h3IsValid(rawLong) == false) {
    throw new IllegalArgumentException("not a valid H3 index: " + rawLong);
}
long[] ring = H3.hexRing(rawLong);
Defensive patterns

Strategy: validation

Validate before calling

static long[] safeRing(long h3) {
    if (!org.elasticsearch.h3.H3.h3IsValid(h3)) {
        throw new IllegalArgumentException("not a valid H3 index: " + h3);
    }
    return org.elasticsearch.h3.H3.hexRing(h3);
}

Type guard

static boolean isUsable(long h3) {
    return org.elasticsearch.h3.H3.h3IsValid(h3);
}

Try / catch

try {
    return H3.hexRing(h3);
} catch (IllegalArgumentException e) {
    if (e.getMessage().equals("Invalid base cell looking for neighbor")) {
        throw new IllegalArgumentException("corrupt or non-H3 index: " + h3, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: An operation that walks neighbors (hexRing, hexRingPosToH3, noChildIntersectingPosToH3, areNeighbours fallback) is given an origin long whose base-cell nibble is invalid. With public API input this is masked by earlier validity checks; it surfaces when a corrupt or non-H3 long is passed in.

Common situations: Passing an opaque long from an external/untrusted source into a neighbor walk without validation; deserialization/endian corruption of an H3 long; mixing indexes from incompatible H3 versions whose base-cell layout differs.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/208fdccfcb657e15. Report an issue: GitHub.