{"record":{"id":"cce77b2141335584","repo":"elastic/elasticsearch","slug":"circle-is-not-supported","errorCode":null,"errorMessage":"Circle is not supported","messagePattern":"Circle is not supported","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"libs/geo/src/main/java/org/elasticsearch/geometry/utils/SpatialEnvelopeVisitor.java","lineNumber":321,"sourceCode":"        }\n\n        private static Rectangle maybeWrap(double top, double bottom, double negLeft, double negRight, double posLeft, double posRight) {\n            double unwrappedWidth = posRight - negLeft;\n            double wrappedWidth = 360 + negRight - posLeft;\n            return unwrappedWidth <= wrappedWidth\n                ? new Rectangle(negLeft, posRight, top, bottom)\n                : new Rectangle(posLeft, negRight, top, bottom);\n        }\n    }\n\n    private boolean isValid() {\n        return pointVisitor.isValid();\n    }\n\n    @Override\n    public Boolean visit(Circle circle) throws RuntimeException {\n        // TODO: Support circle, if given CRS (needed for radius to x/y coordinate transformation)\n        throw new UnsupportedOperationException(\"Circle is not supported\");\n    }\n\n    @Override\n    public Boolean visit(GeometryCollection<?> collection) throws RuntimeException {\n        collection.forEach(geometry -> geometry.visit(this));\n        return isValid();\n    }\n\n    @Override\n    public Boolean visit(Line line) throws RuntimeException {\n        for (int i = 0; i < line.length(); i++) {\n            pointVisitor.visitPoint(line.getX(i), line.getY(i));\n        }\n        return isValid();\n    }\n\n    @Override\n    public Boolean visit(LinearRing ring) throws RuntimeException {","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/geo/src/main/java/org/elasticsearch/geometry/utils/SpatialEnvelopeVisitor.java#L303-L339","documentation":"SpatialEnvelopeVisitor computes the bounding box (envelope/MBR) of a Geometry by walking its points. Its visit(Circle) override unconditionally throws UnsupportedOperationException because converting a circle's radius (in meters) into x/y extents requires a Coordinate Reference System, which the visitor does not carry. The TODO at line 320 makes this an explicit, known limitation rather than a bug. Until CRS-aware circle expansion is implemented, no Circle — empty or not — can pass through this visitor.","triggerScenarios":"Calling SpatialEnvelopeVisitor.visitCartesian(circle), SpatialEnvelopeVisitor.visitGeo(circle, wrap), or geometry.visit(new SpatialEnvelopeVisitor(...)) where geometry is or contains (via GeometryCollection recursion at line 326) a Circle. Note that the collection visit forEach at line 326 will recurse into a Circle child and re-trigger this throw.","commonSituations":"Indexing geo_shape documents that use circle geometry and then running a query/aggregation that derives an envelope (e.g. for tile/bbox-based filtering, sorting, or _select_shape). Passing user-supplied or third-party GeoJSON/WKT that was parsed into a Circle directly to any code path that calls SpatialEnvelopeVisitor. Migrating data that previously stored circles into a query path that previously only handled polygons.","solutions":["Filter out or reject Circle geometries before calling the visitor: branch on geometry instanceof Circle (or check geometry.type() == ShapeType.CIRCLE) and skip envelope computation for those documents.","If you need an envelope for a circle, expand the circle into a Polygon approximation (a bounded number of points around the circumference using the known CRS) before invoking the visitor.","For GeometryCollection inputs, pre-flatten and drop any Circle children (or recurse manually skipping Circle) so the visitor's collection.forEach at line 326 never reaches visit(Circle).","Wrap the call in try/catch(UnsupportedOperationException) only as a last resort — this error is deterministic, not transient, so prevention is preferable."],"exampleFix":"// before\nOptional<Rectangle> bbox = SpatialEnvelopeVisitor.visitGeo(geometry, WrapLongitude.WRAP);\n\n// after\nif (geometry instanceof Circle || containsCircle(geometry)) {\n    return Optional.empty(); // circles unsupported by envelope visitor\n}\nOptional<Rectangle> bbox = SpatialEnvelopeVisitor.visitGeo(geometry, WrapLongitude.WRAP);","handlingStrategy":"type-guard","validationCode":"boolean isEnvelopeSupported(Geometry g) {\n    if (g instanceof Circle) return false;\n    if (g instanceof GeometryCollection<?> c) {\n        return c.stream().allMatch(isEnvelopeSupported::apply); // recurse\n    }\n    return true;\n}","typeGuard":"static boolean isCircleOrContainsCircle(Geometry g) {\n    if (g instanceof Circle) return true;\n    if (g instanceof GeometryCollection<?> c) {\n        for (Geometry child : c) if (isCircleOrContainsCircle(child)) return true;\n    }\n    return false;\n}","tryCatchPattern":"try {\n    Optional<Rectangle> bbox = SpatialEnvelopeVisitor.visitGeo(geometry, WrapLongitude.WRAP);\n} catch (UnsupportedOperationException e) {\n    // circle encountered — skip envelope for this document\n    logger.warn(\"skipping envelope: circle unsupported\", e);\n}","preventionTips":["Branch on geometry type before envelope computation; treat Circle as unsupported.","When indexing circles, also store a precomputed bounding Polygon so query paths never need envelope-of-circle.","Validate incoming shapes at the API boundary and reject or transform Circle before they reach visitor code."],"tags":["geo","geometry","circle","spatial-envelope","unsupported-operation","elasticsearch"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}