A lightweight, RFC 7946-compliant GeoJSON library for Java
geojson4j is a Java domain library for working with GeoJSON (RFC 7946) data. It lets you create, parse, validate, and serialize all standard GeoJSON types using a fluent, type-safe API built on top of Jackson.
- ✅ RFC 7946 compliant — full specification coverage
- 🔒 Immutable domain objects — thread-safe by design
- ⚡ Eager validation — factory methods throw on invalid data; constructors stay deserialization-friendly
- 🔌 Jackson-native — drop-in serialization/deserialization with
@JsonCreator/@JsonSubTypespolymorphism - 🧪 Well-tested — serialization, deserialization, invalid states, and validation error keys all covered
| GeoJSON Type | Java Class | RFC 7946 Section |
|---|---|---|
Point |
Point |
§3.1.2 |
LineString |
LineString |
§3.1.4 |
Polygon |
Polygon |
§3.1.6 |
MultiPoint |
MultiPoint |
§3.1.3 |
MultiLineString |
MultiLineString |
§3.1.5 |
MultiPolygon |
MultiPolygon |
§3.1.7 |
GeometryCollection |
GeometryCollection |
§3.1.8 |
Feature |
Feature |
§3.2 |
FeatureCollection |
FeatureCollection |
§3.3 |
Requirements: Java 21+, Maven or Gradle
<dependency>
<groupId>io.github.nramc</groupId>
<artifactId>geojson4j</artifactId>
<version>${geojson4j.version}</version>
</dependency>implementation 'io.github.nramc:geojson4j:1.0.17'Check Maven Central for the latest version.
Use the static of(...) factory methods. They validate eagerly and throw GeoJsonValidationException if the data is
invalid.
// Point — longitude, latitude
Point point = Point.of(102.0, 0.5);
// Point with altitude
Point point3d = Point.of(102.0, 0.5, 256.0);
// LineString — varargs of Position
LineString line = LineString.of(
Position.of(102.0, 0.0),
Position.of(103.0, 1.0),
Position.of(104.0, 0.0)
);
// Polygon — exterior ring must have ≥ 4 positions; first == last (closed)
// PolygonCoordinates.of() accepts one or more List<Position> rings (first = exterior)
List<Position> exterior = List.of(
Position.of(100.0, 0.0),
Position.of(101.0, 0.0),
Position.of(101.0, 1.0),
Position.of(100.0, 1.0),
Position.of(100.0, 0.0) // close the ring
);
Polygon polygon = Polygon.of(exterior);
// Feature — id (nullable), geometry, properties
Map<String, Serializable> props = new HashMap<>();
props.put("name", "My Location");
props.put("category", "landmark");
Feature feature = Feature.of("location-1", point, props);
// FeatureCollection
FeatureCollection collection = FeatureCollection.of(List.of(feature));ObjectMapper mapper = new ObjectMapper();
Point point = Point.of(102.0, 0.5);
String json = mapper.writeValueAsString(point);
// {"type":"Point","coordinates":[102.0,0.5]}Deserialize to a concrete type:
String json = """
{ "type": "Point", "coordinates": [102.0, 0.5] }
""";
ObjectMapper mapper = new ObjectMapper();
Point point = mapper.readValue(json, Point.class);
double lon = point.getCoordinates().getLongitude(); // 102.0
double lat = point.getCoordinates().getLatitude(); // 0.5Deserialize to the base type and let Jackson resolve the subtype via the type field:
GeoJson geoJson = mapper.readValue(json, GeoJson.class); // polymorphic root
Geometry geometry = mapper.readValue(json, Geometry.class); // geometry-only polymorphicConstructors (used during deserialization) do not validate eagerly — this lets you inspect partially-populated
objects. Use validate() for detailed error information or isValid() for a quick check.
// Quick boolean check
GeoJson geoJson = mapper.readValue(json, GeoJson.class);
boolean valid = geoJson.isValid();
// Detailed validation result
ValidationResult result = geoJson.validate();
if (result.hasErrors()) {
result.getErrors().forEach(error ->
System.out.printf("Field: %-30s Key: %-40s Message: %s%n",
error.getField(), error.getKey(), error.getMessage())
);
}Stable validation error keys you can rely on in code:
| Key | Meaning |
|---|---|
type.invalid |
type field is missing or wrong |
coordinates.invalid.empty |
coordinates is null or empty |
coordinates.latitude.invalid |
latitude is out of the valid range |
coordinates.longitude.invalid |
longitude is out of the valid range |
coordinates.invalid.minimum.positions |
not enough positions (e.g., polygon ring < 4) |
coordinates.invalid.ring.closed |
polygon ring is not closed (first ≠ last) |
geometries.invalid.nested.geometry |
GeometryCollection contains another one |
try {
// Latitude 999 is out of range → throws immediately
Point invalid = Point.of(102.0, 999.0);
} catch (GeoJsonValidationException ex) {
// handle validation failure
}GeoJson (sealed)
├── Geometry (sealed)
│ ├── Point
│ ├── LineString
│ ├── Polygon
│ ├── MultiPoint
│ ├── MultiLineString
│ ├── MultiPolygon
│ └── GeometryCollection
├── Feature
└── FeatureCollection
| Type | Description |
|---|---|
GeoJson |
Sealed root; Jackson polymorphism resolved via type |
Geometry |
Sealed base for all geometry types |
Position |
Longitude / latitude / optional altitude coordinate tuple |
PolygonCoordinates |
Enforces ring rules (≥ 4 positions, closed) |
Validatable |
Interface providing validate(), isValid(), hasErrors() |
ValidationResult |
Holds a set of ValidationError instances |
GeoJsonValidationException |
Thrown by factory methods on invalid input |
A runnable Spring MVC example is available in examples/spring-mvc-example. It
demonstrates:
- Echo endpoint — round-trip serialization of any
GeoJsonobject - Validation endpoint — returns
ValidationResultas JSON - CRUD endpoint — persists and retrieves
GeoJsonusing JPA + H2
# Run tests only
mvn test
# Full build including Checkstyle
mvn verify
# With code-coverage report
mvn -Pcoverage verify
# Apply OpenRewrite recipes (license headers, static-analysis fixes …)
mvn -Popen-rewrite process-sources
# Dry-run OpenRewrite (check without applying)
mvn -Popen-rewrite prepare-packageCode style is enforced by Checkstyle (config/checkstyle/geojson4j_checks.xml):
- Google-style base
- 160-character line limit
- No wildcard imports
Contributions are very welcome! Here's how to get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes following the Conventional Commits format
- Push the branch:
git push origin feature/my-feature - Open a Pull Request — the CI pipeline will run automatically
Please open an issue on GitHub Issues to report bugs or request features before starting a large change.
Follow the pattern established by existing types:
- Add the type constant in
GeoJsonType.java - Register
@JsonSubTypesentries in bothGeoJsonandGeometry - Implement the class: no-arg constructor +
@JsonCreatorconstructor +of(...)factory +validate()+equals/hashCode/toString - Add tests covering serialization, base-type deserialization, invalid states, and eager-validation exceptions
Distributed under the Apache License 2.0. See LICENSE for full text.
| Tool | Purpose |
|---|---|
| Jackson | JSON serialization / deserialization |
| SonarCloud | Static analysis & quality gate |
| OpenRewrite | Automated code refactoring |
| Renovate | Automated dependency updates |
Ramachandran Nellaiyappan
⭐ If you find this project useful, please give it a star — it helps a lot!