elastic/elasticsearch · error · IllegalArgumentException
invalid ring position
Error message
invalid ring position
What it means
Thrown by H3.hexRingPosToH3 when the effective position is not in [0,5]. For pentagons, positions >= 2 are shifted by +1 (to skip the missing k-axis direction), so a raw ringPos of 6 on a pentagon maps to effective 7 and is caught; negative positions always throw. Hexagons accept 0..5 directly.
Source
Thrown at libs/h3/src/main/java/org/elasticsearch/h3/H3.java:404
* @param h3Address Origin index
* @return the number of neighbor indexes from the origin
*/
public static int hexRingSize(String h3Address) {
return hexRingSize(stringToH3(h3Address));
}
/**
* Returns the neighbor index at the given position.
*
* @param h3 Origin index
* @param ringPos position of the neighbour index
* @return the actual neighbour at the given position
*/
public static long hexRingPosToH3(long h3, int ringPos) {
// for pentagons, we skip direction at position 2
final int pos = H3Index.H3_is_pentagon(h3) && ringPos >= 2 ? ringPos + 1 : ringPos;
if (pos < 0 || pos > 5) {
throw new IllegalArgumentException("invalid ring position");
}
return HexRing.h3NeighborInDirection(h3, HexRing.DIRECTIONS[pos].digit());
}
/**
* Returns the neighbor index at the given position.
*
* @param h3Address Origin index
* @param ringPos position of the neighbour index
* @return the actual neighbour at the given position
*/
public static String hexRingPosToH3(String h3Address, int ringPos) {
return h3ToString(hexRingPosToH3(stringToH3(h3Address), ringPos));
}
/**
* returns whether or not the provided hexagons border
*View on GitHub (pinned to db6a809a66)
Solutions
- Bound the loop by H3.hexRingSize(cell) (6 hex / 5 pentagon), never a hardcoded 6.
- Validate 0 <= ringPos < hexRingSize(cell) before calling.
- Remember pentagons have 5 ring positions; let hexRingSize drive the bound.
Example fix
// before
for (int i = 0; i <= 6; i++) { // throws on pentagons / on i=6
long nb = H3.hexRingPosToH3(cell, i);
}
// after
for (int i = 0; i < H3.hexRingSize(cell); i++) {
long nb = H3.hexRingPosToH3(cell, i);
} Defensive patterns
Strategy: validation
Validate before calling
static long safeRingPos(long h3, int pos) {
int max = org.elasticsearch.h3.H3.hexRingSize(h3); // 6 hex / 5 pentagon
if (pos < 0 || pos >= max) {
throw new IllegalArgumentException("ringPos " + pos + " out of [0," + max + ")");
}
return org.elasticsearch.h3.H3.hexRingPosToH3(h3, pos);
} Type guard
static boolean validRingPos(long h3, int pos) {
return pos >= 0 && pos < org.elasticsearch.h3.H3.hexRingSize(h3);
} Try / catch
try {
return H3.hexRingPosToH3(h3, pos);
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("invalid ring position")) {
throw new IndexOutOfBoundsException("ringPos " + pos + " invalid for " + h3);
}
throw e;
} Prevention
- Always bound loops by hexRingSize (5 for pentagons).
- Never assume 6 neighbors; pentagons have 5.
- Prefer H3.hexRing(h3) over manual position iteration.
When it happens
Trigger: Calling hexRingPosToH3(cell, n) with n negative, or n > 5 on a hexagon, or n > 5 on a pentagon (since pentagons remap n>=2 and thus n=6 -> effective 7 throws). The public H3.hexRing iterates 0..hexRingSize-1 (5 or 6), so direct callers passing unbounded n are the risk.
Common situations: Caller iterating 0..6 unconditionally instead of using hexRingSize; assuming 6 neighbors for pentagons (they have 5); passing a ring position sourced from a different H3 version's enumeration.
Related errors
- invalid child position
- Illegal base cell
- Input is a base cell
- Invalid child resolution [{}]
- resolution [{}] is out of range (must be 0 <= res <= 15)
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/8228a28a3fce9884.
Report an issue: GitHub.