Let's get started with a Microservice Architecture with Spring Cloud:
Geospatial Operations Using JTS
Last updated: September 4, 2025
1. Overview
Geospatial capabilities are essential in today’s software landscape, whether we’re building delivery apps, drone zones, fleet tracking, or geographic analytics. Java Topology Suite (JTS) is a geometry engine written in Java that provides a rich API for modeling and processing 2D spatial data.
In this tutorial, we’ll explore the essentials of JTS through code examples and real-world use cases.
2. Set Up JTS
To start using JTS in our Java project, we need to add the Maven dependency:
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.20.0</version>
</dependency>
3. Creating Geometries Using WKT
Before performing geospatial operations, we must first define our spatial data. JTS supports Well-Known Text (WKT), a standardized format for describing geometry types, such as points, lines, and polygons.
Here is the WKT geometry syntax:
- POINT (x, y): a single coordinate, e.g., POINT (10, 20)
- POLYGON ((x1 y1, x2 y2, …, xN yN)): a closed polygon, first and last points must be the same
Let’s create a GeometryFactoryUtil class and a readWKT() method to read geometry in the WKT format:
public class GeometryFactoryUtil {
public static Geometry readWKT(String wkt) throws Exception {
WKTReader reader = new WKTReader();
return reader.read(wkt);
}
}
3.1. Containment: Is a Point Inside a Polygon?
The contains() method checks whether one geometry is completely within another. This is frequently used in geofencing, zoning, and tracking applications.
First, let’s create a new class called JTSOperationUtils and then create our method to check if a point is inside the polygon:
public class JTSOperationUtils {
private static final Logger log = LoggerFactory.getLogger(JTSOperationUtils.class);
public static boolean checkContainment(Geometry point, Geometry polygon) {
boolean isInside = polygon.contains(point);
log.info("Is the point inside polygon? {}", isInside);
return isInside;
}
}
Now let’s create our test to check if a point is inside the polygon:
@Test
public void givenPolygon2D_whenContainPoint_thenContainmentIsTrue() throws Exception {
Geometry point = GeometryFactoryUtil.readWKT("POINT (10 20)");
Geometry polygon = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 40, 40 40, 40 0, 0 0))");
Assert.assertTrue(JTSOperationUtils.checkContainment(point, polygon));
}
The point (10, 20) lies within a square polygon that spans from (0, 0) to (40, 40). The contains() method returns true when the point is entirely inside the polygon boundaries.
3.2. Intersection: Do Two Geometries Overlap?
The intersects() method checks if two geometries touch or overlap. We can also use the intersection() method to determine the overlapping region.
Let’s add a new method called checkIntersect() to our JTSOperationUtils to check if two geometries intersect each other and to determine the overlapping region:
public static boolean checkIntersect(Geometry rectangle1, Geometry rectangle2) {
boolean intersect = rectangle1.intersects(rectangle2);
Geometry overlap = rectangle1.intersection(rectangle2);
log.info("Do both rectangle intersect? {}", intersect);
log.info("Overlapping Area: {}", overlap);
return intersect;
}
The two rectangles intersect partially. The intersects() method returns true, and the intersection() method gives the geometry of the overlapping area.
Now let’s add a new test to check if two geometries intersect each other:
@Test
public void givenRectangle1_whenIntersectWithRectangle2_thenIntersectionIsTrue() throws Exception {
Geometry rectangle1 = GeometryFactoryUtil.readWKT("POLYGON ((10 10, 10 30, 30 30, 30 10, 10 10))");
Geometry rectangle2 = GeometryFactoryUtil.readWKT("POLYGON ((20 20, 20 40, 40 40, 40 20, 20 20))");
Assert.assertTrue(JTSOperationUtils.checkIntersect(rectangle1, rectangle2));
}
The checkIntersect() function checks whether two geometries (in this case, rectangles) overlap by using the intersects method. The unit test confirms this behavior by defining two rectangles that partially overlap and asserting that checkIntersect() returns true, proving the intersection detection works correctly.
3.3. Buffer: Create a Radius Around a Point
The buffer() method creates a circular or polygonal area around a geometry. It’s commonly used for proximity detection or safe zone modeling.
We’ll create a new method called getBuffer() in our utils class to handle adding a buffer to our geometry point:
public static Geometry getBuffer(Geometry point, integer intBuffer) {
Geometry buffer = point.buffer(intBuffer);
log.info("Buffer Geometry: {}", buffer);
return buffer;
}
This method creates a buffer zone around a specified geometry (typically a point in this case). In geospatial processing, a buffer represents an area surrounding a geometry at a specified distance. It’s commonly used for proximity analysis, such as finding all features within a certain distance from a location.
Now let’s add a new test to assert a new buffer geometry contains our original geometry:
@Test
public void givenPoint_whenAddedBuffer_thenPointIsInsideTheBuffer() throws Exception {
Geometry point = GeometryFactoryUtil.readWKT("POINT (10 10)");
Geometry bufferArea = JTSOperationUtils.getBuffer(point, 5);
Assert.assertTrue(JTSOperationUtils.checkContainment(point, bufferArea));
}
The getBuffer() function creates a buffer area around a given geometry (like a point) at a specified distance, returning a polygon that represents the surrounding zone. The unit test verifies this by creating a point (10,10), generating a buffer of 5 units around it, and checking that the point is indeed inside the resulting buffer, ensuring the buffer operation works correctly.
3.4. Distance: How Far Apart Are Two Points?
The distance() method calculates the Euclidean Distance between two geometries. This is useful in nearest location searches and alerts.
We’ll name this method getDistance() in our utils class:
public static double getDistance(Geometry point1, Geometry point2) {
double distance = point1.distance(point2);
log.info("Distance: {}",distance);
return distance;
}
This method is designed to calculate the distance between two geometric objects, typically points, using the JTS Geometry library. If both inputs are points, the result will be the Euclidean Distance. For other types of geometries, such as lines or polygons, it represents the minimum distance between their boundaries.
Now, let’s add a new test to assert that the distance we get from the distance() method is correct:
@Test
public void givenTwoPoints_whenGetDistanceBetween_thenGetTheDistance() throws Exception {
Geometry point1 = GeometryFactoryUtil.readWKT("POINT (10 10)");
Geometry point2 = GeometryFactoryUtil.readWKT("POINT (13 14)");
double distance = JTSOperationUtils.getDistance(point1, point2);
double expectedResult = 5.00;
double delta = 0.00;
Assert.assertEquals(expectedResult, distance, delta);
}
This test calculates the distance between the two points (10, 10) and (13, 14), which returns 5.0 units from the Euclidean calculator.
3.5. Union: Merge Two Polygons
The union() method combines two geometries into a single shape. This is useful for merging areas, such as land parcels or administrative zones.
We’ll add a new method called getUnion() in our utils class to combine 2 geometries:
public static Geometry getUnion(Geometry geometry1, Geometry geometry2) {
Geometry union = geometry1.union(geometry2);
log.info("Union Result: {}", union);
return union;
}
This test is written to verify that the getUnion() method correctly combines two adjacent polygons into a single larger polygon:
@Test
public void givenTwoGeometries_whenGetUnionOfBoth_thenGetTheUnion() throws Exception {
Geometry geometry1 = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))");
Geometry geometry2 = GeometryFactoryUtil.readWKT("POLYGON ((10 0, 10 10, 20 10, 20 0, 10 0))");
Geometry union = JTSOperationUtils.getUnion(geometry1, geometry2);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 20 10, 20 0, 10 0, 0 0))");
Assert.assertEquals(expectedResult, union);
}
In this test, two square polygons are defined adjacent to each other, sharing a common edge. Two adjacent rectangles are combined into one larger polygon that spans from (0, 0) to (20, 10). The assertion confirms that the union operation produces the correct geometry, ensuring that the implementation works as intended.
3.6. Difference: Subtract One Shape From Another
The difference() method subtracts one geometry from another, for example, to remove restricted zones or masked areas.
Let’s define a new method getDifference() in our utils class:
public static Geometry getDifference(Geometry base, Geometry cut) {
Geometry result = base.difference(cut);
log.info("Resulting Geometry: {}", result);
return result;
}
This method takes two geometries as input, where the first one acts as the base shape and the second one represents the shape to be cut out.
Let’s add a new unit test to check whether the getDifference() method can correctly subtract one rectangle from another when they overlap.:
@Test
public void givenBaseRectangle_whenAnotherRectangleOverlapping_thenGetTheDifferenceRectangle() throws Exception {
Geometry base = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))");
Geometry cut = GeometryFactoryUtil.readWKT("POLYGON ((5 0, 5 10, 10 10, 10 0, 5 0))");
Geometry result = JTSOperationUtils.getDifference(base, cut);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 0 10, 5 10, 5 0, 0 0))");
Assert.assertEquals(expectedResult, result);
}
In this test, the base rectangle spans from (0,0) to (10,10), while the second rectangle overlaps the right half of it from (5,0) to (10,10). The getDifference() method subtracts the overlapping area, leaving only the left half of the base rectangle, which stretches from (0,0) to (5,10). The test then checks if the output matches the expected remaining shape, ensuring that the difference operation works as intended.
3.7. Geometry Validation and Repair
Invalid geometries (e.g., self-intersecting polygons) can cause problems in spatial computations. JTS offers the isValid() method to check validity and the buffer(0) method to auto-correct them.
Let’s add a new method called validateAndRepair() to our utils class:
public static Geometry validateAndRepair(Geometry invalidGeo) throws Exception {
boolean valid = invalidGeo.isValid();
log.info("Is valid Geometry value? {}", valid);
Geometry repaired = invalidGeo.buffer(0);
log.info("Repaired Geometry: {}", repaired);
return repaired;
}
The input polygon is self-intersecting. Applying the buffer(0) method corrects it by reconstructing a valid geometry from the invalid input. JTS was only able to extract a small, valid triangle from the original invalid bowtie shape, and only the part that formed a valid enclosed shape was kept.
Let’s add a new test to ensure the expected geometry after the buffer(0) method fix is the same as the actual result we got:
@Test
public void givenInvalidGeometryValue_whenValidated_thenGiveFixedResult() throws Exception {
Geometry invalidGeo = GeometryFactoryUtil.readWKT("POLYGON ((0 0, 5 5, 5 0, 0 5, 0 0))");
Geometry result = JTSOperationUtils.validateAndRepair(invalidGeo);
Geometry expectedResult = GeometryFactoryUtil.readWKT("POLYGON ((2.5 2.5, 5 5, 5 0, 2.5 2.5))");
Assert.assertEquals(expectedResult, result);
}
The original value of geometry that is invalid creates a self-intersection that JTS can’t cleanly break into two valid parts. So it conservatively keeps only the largest valid ring it can find, in this case, the triangle near the right side.
4. Conclusion
In this article, we learned that the Java Topology Suite (JTS) makes working with spatial data in Java intuitive and powerful. It supports advanced geometric modeling, validation, and spatial computations, all of which are essential for geospatial applications.
We now have hands-on knowledge of:
- Constructing geometries with WKT
- Performing spatial operations (contain, buffer, distance, union, etc.)
- Handling invalid geometries
- Applying these tools in real-world scenarios
Whether we’re building a mapping tool, a logistics engine, or a location-aware app, JTS equips us with the core tools to work spatially at scale and with precision.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.

















