BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News JEP 540 Proposed to Target JDK 28 with a Simple JSON API

JEP 540 Proposed to Target JDK 28 with a Simple JSON API

Listen to this article -  0:00

JEP 540, Simple JSON API (Incubator), has moved from Candidate to Proposed to Target status for JDK 28. It would add a compact, JDK-provided API for parsing, navigating, and generating RFC 8259 JSON documents without an external dependency.

If targeted and delivered, the API will ship in the incubating jdk.incubator.json module, where it can change incompatibly or be removed based on developer feedback. The proposal is deliberately narrower than libraries such as Jackson and Gson. It excludes data binding and streaming and offers no permissive parsing mode or syntax extensions. Instead, it targets common tasks such as reading a configuration file, inspecting a REST response, or generating a small JSON payload.

The API centers on the Json class and the sealed JsonValue interface. JsonValue permits six non-sealed subinterfaces representing JSON objects, arrays, strings, numbers, booleans, and null. Instances are immutable and thread-safe.

Parsing a complete in-memory document with Json.parse(String) or Json.parse(char[]) returns a JsonValue:

String body = ...; // JSON response body

int temperature = Json.parse(body)
    .get("properties")
    .get("periods")
    .get(0)
    .get("temperature")
    .asInt();

The central design choice is to declare access methods directly on JsonValue, so callers can traverse objects and arrays without repeatedly casting intermediate values. Calling get(String) on a non-object, get(int) on a non-array, requesting a missing member, or using an invalid index throws JsonValueException.

The main ergonomic trade-off appears in document construction. JSON values are created through factory methods on the corresponding interfaces: 

JsonObject document = JsonObject.of(Map.of(
"service", JsonString.of("web_server"),
"id", JsonNumber.of(3),
"active", JsonBoolean.of(true)
));

Calling toString() on a JsonValue generates compact JSON, while Json.toDisplayString(...) produces a formatted representation intended for display. The explicit factories make each value’s JSON type clear, but also add ceremony because ordinary Java strings, numbers, and booleans must be wrapped before being placed in an object or array. Construction ergonomics are consequently one likely area for feedback during incubation.

Strictness is another defining choice. The parser provides no lenient mode, so comments, trailing commas, and other syntax extensions are rejected. It also rejects duplicate object-member names, even though RFC 8259 says names should be unique rather than making uniqueness an absolute requirement. Different parsers may otherwise retain the first value, retain the last, preserve every occurrence, or reject the document, making duplicate names an interoperability risk.

Invalid syntax and duplicate names produce an unchecked JsonParseException, which records the zero-based line and position of the detected error. Its public API does not expose a structured JSON path.

The sealed value hierarchy also works naturally with pattern matching. A producer that emits an identifier as either a JSON number or a string can be handled with a type-pattern switch:

long id = switch (json.get("id")) {
    case JsonNumber number -> number.asLong();
    case JsonString string -> Long.parseLong(string.asString());
    default -> throw new JsonValueException("Unexpected id type");
};

Conversion methods follow the as... naming convention. asInt() and asLong() require an exact integral value within the destination type’s range. asDouble() converts a number to a finite double, but may round or lose precision. asBoolean(), asMap(), and asList() expose booleans, objects, and arrays as Java values. The map and list views are unmodifiable and still contain JsonValue instances rather than recursively converted Java primitives. Applying a conversion to the wrong JSON type throws JsonValueException.

For optional object members, tryGet(String) returns an Optional<JsonValue> that is empty when the member is absent. Invoking it on a value that is not a JSON object still throws JsonValueException. The API also distinguishes a missing member from one explicitly containing JSON null: tryGet() returns an Optional containing JsonNull for the latter, while tryValue() returns an empty Optional for JsonNull.

JEP 198, Light-Weight JSON API, is a broader proposal created in 2014 that was never delivered. The newer design concentrates on an immutable, in-memory value hierarchy and leaves object mapping, streaming, schema validation, and advanced customization to the existing JSON ecosystem.

If JEP 540 becomes Targeted and is delivered, class-path applications will need to resolve the incubator module explicitly with --add-modules jdk.incubator.json. The incubation period will allow the OpenJDK community to evaluate the navigation model, exception semantics, numeric conversions, and construction ergonomics before the API advances further.

About the Author

Rate this Article

Adoption
Style

BT