JEP draft: Simple JSON API
| Authors | Naoto Sato, Paul Sandoz, Justin Lu, Stuart Marks |
| Owner | Naoto Sato |
| Type | Feature |
| Scope | JDK |
| Status | Submitted |
| Component | core-libs |
| Discussion | core dash libs dash dev at openjdk dot org |
| Effort | M |
| Duration | M |
| Reviewed by | Alex Buckley |
| Created | 2024/11/13 23:54 |
| Updated | 2026/04/29 17:36 |
| Issue | 8344154 |
Summary
Provide a simple API to help Java developers read and write JSON documents without the overhead of installing an external library. Many useful JSON processing tasks can be accomplished with minimal coding. This is an incubating API.
History
This JEP supersedes JEP 198, Light-Weight JSON API. Circumstances have changed in the intervening years, and this JEP is pursuing a different approach.
Goals
-
Provide the ability to process RFC 8259-compliant JSON documents with the lowest possible ceremony.
-
Avoid requiring developers to add dependencies in order to consume and produce JSON data.
-
Provide operations conforming strictly to RFC 8259, to facilitate machine-to-machine communications, while avoiding JSON variants, multiple parsing configurations, and optional syntactic features in order to keep the API simple.
-
Implement a limited set of JSON processing features, focusing on fundamental JSON data types, querying, and extraction, while omitting operations such as streaming, data binding, and serialization. This keeps the API small and easy to learn. See the Alternatives section for discussion.
-
Ensure that the JDK itself is capable of consuming and producing JSON, using only built-in mechanisms.
Non-Goals
- It is not a goal to create an API that supplants established external JSON libraries.
Motivation
JSON is ubiquitous in modern computing. Java developers can choose from a wide range of established JSON libraries: Jackson, Gson, Jakarta JSON Processing and Binding, FastJson2, and many more. These libraries provide not only parsing of JSON documents, but also higher level features such as data binding (converting Java objects to and from JSON with a high degree of customization), event-based streaming, and parsing of extended JSON syntax such as JSON5.
However, developers often need to perform simple tasks such as extracting and processing data from JSON documents. These tasks are simple in languages such as Python or Go. It should be equally simple to accomplish such tasks in Java. For example, consider the task of computing the average of a set of forecast temperatures extracted from a response to the National Weather Service REST API. The response is a JSON document that looks like this:
{
...
"properties": {
...
"periods": [
{
"number": 1,
"name": "Today",
"startTime": "2026-04-22T06:00:00-04:00",
"endTime": "2026-04-22T18:00:00-04:00",
"isDaytime": true,
"temperature": 54,
"temperatureUnit": "F",
...
},
{
"number": 2,
"name": "Tonight",
"startTime": "2026-04-22T18:00:00-04:00",
"endTime": "2026-04-23T06:00:00-04:00",
"isDaytime": false,
"temperature": 48,
"temperatureUnit": "F",
...
},
{
"number": 3,
"name": "Thursday",
"startTime": "2026-04-23T06:00:00-04:00",
"endTime": "2026-04-23T18:00:00-04:00",
"isDaytime": true,
"temperature": 68,
"temperatureUnit": "F",
...
},
...
]
}
}
The task involves parsing the document, navigating to the location in the structure that contains the forecasts, and iterating the array of forecasts, while extracting the temperature data. (A complete example of this is shown in the Appendix.) It would be ideal if developers could tackle simple tasks like this in Java without installing an external library or feeling that another language would make them more productive.
Java has made great strides in handling simple tasks with short
no-ceremony programs -- var declarations, convenience methods for
collections, compact source files (no more public static void main),
running programs from source (java Task.java), and so on. Offering
a simple JSON API to extract data from JSON documents continues
this trend.
JSON support in the JDK also paves the way for use of JSON by
the JDK, because the JDK cannot have external dependencies. One potential
use case is for configuration files. The JDK uses
Properties
format for several configuration files, such as java.security.
A weakness of Properties format is that it has no support for storing
structured data. For example, to represent an array in a Properties
file, one can use a series of sequentially numbered properties -- a
clumsy workaround at best:
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
...
With JSON built into the JDK, configuration files can represent arrays quite naturally using JSON arrays.
In addition, the JDK can produce diagnostic output in JSON. For
example, the following jcmd command produces a thread dump
in JSON format:
jcmd <pid> Thread.dump_to_file -format=json threaddump.out
A portion of the output file looks like this:
{
"name": "workerThread",
"tid": 28,
"stack": [
"java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:388)",
"java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:352)",
"java.base\/java.lang.VirtualThread.park(VirtualThread.java:515)"
]
}
It is extremely useful to be able to process this data using only mechanisms built into the JDK.
Description
The jdk.incubator.json.Json
class provides a simple parsing experience for in-memory JSON documents
that conform to RFC 8259. It returns a tree of
jdk.incubator.json.JsonValue
instances that expose the names, types, and values of JSON data. Returning to
the National Weather Service example from the Motivation section, the average
temperature forecast can be parsed and computed in just a few lines:
String body = /* REST response body, a JSON document */ ;
JsonValue json = Json.parse(body);
json.get("properties").get("periods").asList().stream()
.mapToInt(j -> j.get("temperature").asInt())
.average()
.ifPresent(IO::println);
The complete example is shown in the Appendix.
Json and JsonValue design principles
Json and JsonValue are designed with the following principles:
-
Make code that navigates known JSON structures simple and readable. JSON documents are schema-less.
JsonValueprovides methods that make it easy to write code to navigate and extract data from documents that have a known structure. Once written, the code becomes the de facto specification of the schema for those JSON documents and serves as a kind of conformance test for them. It is thus advantageous for the code that validates document structure to be simple and easy to read. -
Each kind of syntactic element in the JSON syntax should have a corresponding type in the API. JSON syntax has four primitives (strings, numbers, booleans, and null) and two structures (objects and arrays). The
JsonValueinterface has six sub-interfaces that correspond directly to these elements. -
Enable the quick exploration of unfamiliar JSON data. Developers often interact with JSON in an exploratory manner, writing code not using a specification but instead trying it out against example documents.
JsonValueprovides methods that fail fast with clear error messages, enabling quick exploration. -
Enable the handling of missing and unexpected values in a resilient fashion. JSON documents can change freely over time, or they can even have differing structure within the same document.
JsonValueprovides techniques to flexibly handle these situations.
JSON syntactic elements and Java types
A JSON document has six kinds of syntactic elements.
- A JSON string, delimited with double quotes:
"Hello"
"My name is 'Bob'"
"\u006a\u0061\u0076\u0061"
- A JSON number, represented in base 10 using decimal digits:
6 6.0 31.84 2.9E+5
- A JSON object, delimited by
{}and composed of comma-separated members. A member has a name (also called a key) and a value, separated by a colon:
{
"address" : "123 Smith Street",
"value" : 31.84,
"coordinates" : [ 37, 23, 41, -121, 57, 10 ]
}
- A JSON array, delimited by
[]and composed of comma-separated JSON values:
[ 1, 2, 3, { "value": "4" }, [ 5, 6 ] ]
-
The JSON boolean literals:
trueandfalse -
The JSON null literal:
null
Each of the JSON syntactic elements has a corresponding Java subtype of the
JsonValue interface. This interface is sealed so that it is not user extensible.
User code can be assured that every JsonValue instance is always one of this
fixed set of subtypes.
sealed interface JsonValue
permits JsonString, JsonNumber, JsonObject, JsonArray, JsonBoolean, JsonNull {
...
}
interface JsonString extends JsonValue { ... }
interface JsonNumber extends JsonValue { ... }
interface JsonObject extends JsonValue { ... }
interface JsonArray extends JsonValue { ... }
interface JsonBoolean extends JsonValue { ... }
interface JsonNull extends JsonValue { ... }
These interfaces offer operations appropriate to their JSON types:
for example,
JsonObject
values expose members,
JsonArray
values
expose elements, and primitive values expose conversions to Java
primitives and strings.
Parsing and navigating JSON documents
Json parses JSON documents as text in the form of either a String or char[].
For example, a JSON document might be a REST API response body read from network,
a configuration file read from disk, or some other text payload produced by
an application. Parsing a JSON document takes only one call to
Json.parse():
JsonValue root = Json.parse(doc);
Parsing is strict: the document must conform to RFC 8259. Syntax extensions such as trailing commas or comments are not supported. In addition, documents must not have objects with duplicate member names. This policy, permitted by the RFC, provides maximum interoperability and predictability, and it reduces concerns about processing malformed or ambiguous JSON documents. See the Alternatives section for a full discussion.
Successful parsing returns an instance of JsonValue. Unsuccessful
parsing throws an unchecked
JsonParseException.
This exception includes a detail message that provides specific information
about the exact error and where it occurred in the document. For example, the
exception thrown from failing to parse a document with duplicate member names in
an object will appear similar to:
jdk.incubator.json.JsonParseException: The duplicate member name: "..." was
already parsed. Location: line ..., position ...
Most JSON documents have a JSON object or JSON array at the root. For example,
consider a JSON-formatted thread dump produced by jcmd which contains a root object:
{
"threadDump": {
"formatVersion": 2,
"processId": 45178,
"time": "2026-04-16T23:13:02.709630Z",
"runtimeVersion": "27-internal",
"threadContainers": [
{
"container": "<root>",
...
The root object contains a single member, the nested threadDump object.
threadDump itself contains both primitive and structural JSON values.
Once you have obtained the root JsonValue with parse, you can
retrieve values from objects and arrays by calling the following
access methods. These methods return the requested member value or array element
as a JsonValue. If the receiver is of the wrong kind, or if the requested
member or element does not exist, they throw
JsonValueException.
get(String)obtains the value of an object member:
For example, to obtain the thread dump object, you can call:
JsonValue threadDump = root.get("threadDump");
get(int)obtains an array element:
For example, to obtain the root thread container, you can call:
JsonValue firstContainer = threadDump.get("threadContainers").get(0);
Converting JSON values to Java values
You can convert a JSON value to a particular Java type by
calling one of the conversion methods that appear on the JsonValue interface.
For a conversion method to succeed, the JsonValue must be an instance
of the appropriate JsonValue subtype. The following table shows
the subtype required for each conversion method:
| Method | JsonValue Subtype |
|---|---|
asString |
JsonString |
asInt |
JsonNumber |
asLong |
JsonNumber |
asDouble |
JsonNumber |
asBoolean |
JsonBoolean |
asMap |
JsonObject |
asList |
JsonArray |
JsonValue threadDumpTime = threadDump.get("time"); // Access "time" first
String time = threadDumpTime.asString();
Conversion methods can also be called on structural JSON values.
For example, once you have navigated to the thread containers array,
you can convert the value to a List of JsonValue, giving you the ability
to process all elements:
threadContainers.asList().forEach(jv -> ...);
Or perhaps you might want to access the thread dump object as a Map to retrieve
the number of members:
int count = threadDump.asMap().size();
You might want to navigate deeply into a JSON document, chaining access methods and converting to a Java type only at the end. For example, to retrieve the thread identifier value for the first thread in the root thread container:
long tid = threadDump.get("threadContainers").get(0)
.get("threads").get(0).get("tid").asLong();
This design of conversion methods eliminates most instanceof
checking and downcasting in cases where a specific JSON data
type is expected in a document.
-
asString()converts aJsonStringinstance into a JavaStringwith RFC 8259 JSON escape sequences translated to their corresponding characters. -
asInt()converts aJsonNumberinstance to a Javaintif its numeric value can be represented exactly. -
asLong()converts aJsonNumberinstance to a Javalongif its numeric value can be represented exactly. -
asDouble()converts aJsonNumberinstance to a Javadoubleif its numeric value can be represented accurately. -
asBoolean()converts aJsonBooleaninstance to a Javabooleanvalue oftrueorfalse. -
asMap()converts aJsonObjectinstance into an unmodifiable JavaMap. If the JSON object contains no members, an emptyMapis returned. -
asList()converts aJsonArrayinstance into an unmodifiable JavaList. If the JSON array contains no elements, an emptyListis returned.
There is no conversion method for the JSON null value. JSON nulls
can be handled with the
tryValue()
method or by testing for
instanceof JsonNull; see
JsonNull.
If a JsonValue is not an instance of the appropriate subtype
for a conversion method, JsonValueException is thrown. For
example, calling asInt() on a JsonValue that is an instance
of JsonString will always throw this exception. No attempt
is made to parse the string value into a number.
Numeric conversions can fail for reasons such as the numeric value not
being representable in the requested Java numeric type.
JsonValueException is also thrown in these cases. See Json
numerics for a deeper discussion of number handling and
conversions.
Handling evolution of JSON documents
JSON documents can change unpredictably, so your expectations about them might not always hold.
-
You might call access methods expecting member names or array indices that do not exist in the JSON objects and JSON arrays of a given document.
-
You might call conversion methods that apply to a particular JSON type on values of a different type.
If you call access or conversion methods on the wrong type, they throw
JsonValueException. This exception is
unchecked, as it is not expected to be handled. This makes scripts and
small programs easier to read and write.
Continuing the thread dump example from before, recall that the root JSON value contains
a single member, "threadDump". The following code throws JsonValueException because
"threadName" does not exist as a member:
JsonValue name = root.get("threadName");
While the following code throws JsonValueException because the "threadDump"
member is a JSON object, not a JSON array:
List<JsonValue> threadDumpList = threadDump.asList();
The exception message describes the route leading from the root of the
JSON document to the unexpected JSON value, as well as the position in
the JSON document. This is helpful when a chain of access methods
navigates deeply into the document. For example, if the earlier code snippet
to access tid incorrectly converted it to a boolean instead of a long:
boolean tid = threadDump.get("threadContainers").get(0)
.get("threads").get(0).get("tid").asBoolean();
The exception thrown by asBoolean() will appear similar to:
jdk.incubator.json.JsonValueException: JsonNumber is not a JsonBoolean.
Path: "...". Location: line ..., position ...
Handling optional members and null values
If you know that a JSON object might or might not have a member with a
given name, you can call the access method
tryGet.
This returns
an Optional containing the value or an empty Optional if the
member does not exist. (This contrasts with get, which confirms that
the member exists and throws an exception if it does not.)
The tryGet() method will throw JsonValueException if
it is not called on a JsonObject.
For example, consider the following thread object:
{
"tid": 11,
"time": "2026-04-16T23:13:02.918321Z",
"name": "Finalizer",
"state": "WAITING",
"waitingOn": "java.lang.Object@c10f5b9",
"stack": [
...
A thread object contains multiple optional members. One of them is the "waitingOn" member, which contains the JSON string representation of the object on which the thread is waiting. However, in cases where the thread is not waiting, the thread object may look like this:
{
"tid": 10,
"time": "2026-04-16T23:13:02.918177Z",
"name": "Reference Handler",
"state": "RUNNABLE",
"stack": [
...
Thus, when processing thread objects from a thread dump, you may need to handle
cases where the member you are interested in may be absent.
You can handle this scenario using tryGet:
JsonValue thread = ...
thread.tryGet("waitingOn")
.ifPresent(result -> ...);
The operation within ifPresent is only performed if waitingOn is present.
If you know that a JSON value might or might not be a JSON null, you can process
it with the method
tryValue,
which returns an Optional. If the JSON
value is a JSON null, the Optional is empty; otherwise, the Optional
contains a JsonValue that is not a JsonNull.
For example, a thread container object typically looks like this:
{
"container": "java.util.concurrent.ThreadPoolExecutor@1936a586",
"parent": "<root>",
...
Here, the "parent" member's value is a JSON string, the parent container's name. However, the container named "<root>" is the root of all containers and looks like this:
{
"container": "<root>",
"parent": null,
...
The root container has no parent, so the "parent" member's value is a JSON null.
Thus, when processing container objects from a thread dump, you may need to handle
the parent member being a JSON string or a JSON null.
You can handle this scenario using tryValue:
JsonValue container = ...
container.get("parent").tryValue()
.ifPresent(result -> ...);
The operation within ifPresent is only performed if "parent" is not JSON null.
Handling structural variations
Most JSON documents have uniform structure, but it's possible for some JSON documents to have variable structure. This variation might occur across different documents produced by different software; or it might occur over time as the document generation software evolves; or it might even occur within the same document.
For example, thread dumps from JDK 27 and later contain thread identifiers as JSON numbers. However, thread dumps from JDK 26 and earlier contain thread identifiers as JSON strings. (This change was made by JDK-8381002, which was integrated in JDK 27, build 16.)
Code that successfully processes the tid as a JSON number:
long tid = thread.get("tid").asLong();
fails with a JsonValueException if it encounters a thread dump produced by
a version of the JDK that emits tid as a JSON string.
You can write code that supports both variations of a thread identifier,
noting that in either format, the value is specified to fit in a Java long.
It's possible to use instanceof to check the type of the value, but in cases
such as this, it may be more effective to use a type pattern within a switch statement:
long tid = switch (thread.get("tid")) {
case JsonNumber jn -> jn.asLong();
case JsonString js -> Long.parseLong(js.asString());
default -> throw new JsonValueException("Unexpected type for \"tid\"");
};
Generating JSON documents
It is often necessary to generate plain text from a JSON document.
The
toString()
method returns a compact string representation where
all members, elements, and values are emitted on the same line with no
whitespace between them.
For example, this code:
JsonValue json = Json.parse("""
{
"service" : "web_server",
"id" : 3
}
""");
IO.println(json.toString());
prints:
{"service":"web_server","id":3}
Note that the behavior of toString() differs from asString(),
which throws an exception if the JsonValue is not a JsonString.
The static method
Json.toDisplayString()
emits a "pretty-printed"
version of a JSON document, where members and elements are separated
by newlines, and nested structures are indented by the given amount.
For example, use the following to print the above structure with two
spaces of indentation:
IO.println(Json.toDisplayString(json, 2));
which prints:
{
"service": "web_server",
"id": 3
}
The output of both toString() and Json.toDisplayString() are
parsable by Json.parse() and will return a JSON structure that is
equivalent to the original.
JSON numerics
RFC 8259 syntax allows decimal numbers with arbitrary precision and
range, and the JDK JSON API allows JSON numbers to be processed
losslessly. This is unnecessary for most applications, for which more
common numeric types are sufficient. RFC 8259 states that good
interoperability among JSON implementations can be achieved by using
IEEE 754 64-bit binary floating point. This format corresponds to
Java's double type.
The asDouble() method converts a numeric JSON value to a Java double. The
JSON value must lie within the range that a double can represent. If the
value is out of range, an exception is thrown; positive or negative infinity
values are never returned. The double value NaN (not-a-number) cannot be
represented in JSON and is also never returned. However, negative zero is
supported.
If the JSON value has more precision than can be represented in a double,
the value is rounded to the closest double value. For example:
double d1 = Json.parse("3.141592653589793238462643383279").asDouble();
// d1 is 3.141592653589793, the nearest double value
double d2 = Json.parse("1.8E309").asDouble();
// throws JsonValueException, out of range
Integral numeric values are quite frequently used, so the asInt() method
converts a numeric JSON value to a Java int value. The JSON value must be
exactly representable as an int, otherwise an exception is thrown. Numbers
that have a syntactic fractional part but that represent integral values are
converted successfully. For example:
int i1 = Json.parse("123.0").asInt(); // succeeds
int i2 = Json.parse("234.56E2").asInt(); // succeeds
int i3 = Json.parse("345.6").asInt(); // fails
int i4 = Json.parse("2147483648").asInt(); // fails
The conversion method asLong() is similar to asInt() except that
it returns a Java long value and supports any JSON numeric value that
can be represented exactly as a long.
If you need a narrower primitive type than int or double, you can use
instanceof primitive matching to perform a safe conversion. (Primitive
Types in Patterns is a preview
feature.) For example, if you expect a JSON
number to be representable with a short, you can do the following:
JsonValue json = Json.parse("""
{
"id": 12345,
"price": 10.99
}
""");
if (json.get("id").asInt() instanceof short s) {
// use s
} else {
// report out-of-range error
}
As mentioned previously, JSON decimal numbers can have arbitrary precision and
range. The asDouble(), asInt(), and asLong() methods by definition
process only a subset of JSON numeric values, as they either reject certain
inputs or discard information via rounding. To preserve JSON numeric data
without loss of information, you can convert essentially any JSON number to a
Java BigDecimal by doing the following:
BigDecimal bd = new BigDecimal(json.toString());
Alternatives
The large set of external JSON libraries that already exist (including those mentioned previously) comprises an extremely broad feature set. The task is therefore to select a subset of these features suitable to include in the JDK. The selected features should provide the greatest value relative to their cost.
We've excluded the commonly provided feature of data binding. This is undeniably useful and convenient for many applications. However, it would add a significant API footprint and increase implementation and maintenance costs dramatically. Moreover, there are use cases that don't require data binding; we thus consider this feature not strictly necessary. That Jackson and Jakarta JSON factor data binding into separate modules is an implicit recognition that there are use cases that don't need data binding and that can exclude this dependency.
A streaming API is clearly essential for certain narrow, specialized use cases. However, it requires a fair amount of complexity in applications in order to perform relatively simple data extraction tasks. We've thus excluded this feature on that basis.
This left us with a DOM-like approach where JSON documents are parsed into a tree of JSON-specific objects from which data can be extracted easily. The API is extremely small, and it incurs correspondingly small implementation and maintenance costs. This satisfies the needs of a significant subset of JSON applications from the very simplest to the moderately complex.
It's possible for an application to start off using the JDK JSON API, but eventually for its needs to grow to include features such as data binding. Such an application will need to migrate to an external library that supports that feature. We don't view that as a flaw, and it isn't sufficient justification to include high-cost features such as data binding in the JDK.
- Integrate an external JSON library.
The JDK could integrate an external library as a downstream fork. This would raise some difficult issues over licensing and governance. There would be continual tension over changes flowing in both directions, arising from potentially different criteria regarding specification quality, compatibility, release schedules, and so forth. It seems likely that these costs, plus the additional maintenance burden on the JDK, would outweigh the benefit of integrating an external library.
- Do nothing, as JSON is already handled completely by external libraries.
Since the JDK cannot have external dependencies, this approach would preclude the JDK itself from adding new features that produce and consume JSON.
Any application that adds an external dependency incurs a cost in doing so. There are probably applications that could benefit from using JSON but that do not, because their cost to add a dependency is too high. Such applications would benefit from having JSON support directly in the JDK.
This has been a longstanding issue with JSON. Early specifications were underdetermined with respect to the handling of duplicate member names. This led to a situation where implementations behaved inconsistently with each other or provided application-settable options to select the desired handling policy for duplicate names. Unfortunately, an object with duplicate names is fundamentally ambiguous. When the issue of duplicate names was discussed on the ES-Discuss mailing list in 2013, concern about prohibiting duplicate names was that doing so would invalidate existing documents. Thus, the "should be unique" wording (instead of "must") was retained, and it has been carried over to current specifications. In particular, RFC 8259 says
The names within an object SHOULD be unique. ... An object whose names are all unique is interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. When the names within an object are not unique, the behavior of software that receives such an object is unpredictable.
The unpredictability arises when the object is processed by a system consisting of multiple, independently-developed JSON implementations. This can lead to hard-to-diagnose errors, security vulnerabilities, decreased interoperability, and general lack of robustness. This phenomenon is discussed in RFC 9413, "Maintaining Robust Protocols".
For these reasons, we have chosen a strict approach where duplicate names are unconditionally treated as errors. The strict approach enables Java developers to have higher assurance over the correctness of documents being read. One hopes that the erroneous documents mentioned in the 2013 ES-Discuss conversation have been corrected in the intervening years, and that the software that produced those documents has been fixed.
- Support trailing commas, comments, or other parsing options.
There are several variants of JSON that support comments or trailing commas within arrays and objects. These options are intended to facilitate hand-editing of JSON documents; for example, see JSON5. Such options are not provided, given this project's focus on machine-to-machine communication.
Indeed, this implementation provides no parsing options at all. Adding options enlarges the testing matrix, opens the possibility for interoperability errors, and increases overall development and maintenance burden.
A common workaround is to use a custom preprocessor. For example, comments can be stripped out before parsing, which is arguably one of the main reasons the JSON syntax itself never introduced comments. The following simple snippet shows how to remove single-line comments (lines starting with #) prior to parsing:
String jsonc = Files.readString(Path.of("somefilewithcomments.json"));
String json = jsonc.replaceAll("(?m)^\\s*#.*$", "");
JsonValue jv = Json.parse(json);
Testing
JSON, which appears straightforward in syntax, has a history of varying behavior across different implementations. Having evolved through multiple specifications, differences in behavior have emerged over time in the handling of certain ambiguous and even malicious JSON inputs. As a last mover, the JDK JSON API benefits from being aware of the wide range of edge case inputs that previous implementations have encountered.
The JDK JSON API will be rigorously tested to ensure that only canonical forms of RFC 8259 JSON can be parsed and generated. This is important for ensuring that this library cannot be used to create inconsistencies when interacting with other JSON implementations within a larger system. To accomplish this, our testing approach will not only add comprehensive unit tests to the JDK and JCK, but also leverage the established conformance suite Parsing JSON is a Minefield, which contains numerous edge case inputs.
Risks and Assumptions
-
We assume that a minimal set of JSON processing methods with sensible default behavior and acceptable performance offers significant value when developers need to write prototypes and short programs.
-
We assume that the input JSON documents can fit in memory (String or char[]). Since we adopt a tree-based model, if we were to allow JSON sources such as a file, issues such as insufficient memory would be possible with large documents. This decision aligns with our minimal design philosophy.
-
A risk of this proposal is that a JSON API built into the JDK may end up being mixed into applications that are already using external JSON libraries, resulting in messiness and confusion. However, we believe this risk is outweighed by the benefits of the new use cases this proposal enables.
-
During the incubation period, we will gather more information about use cases involving generating and transforming JSON documents, in order to evolve these areas of the API. In addition, we will continue to consider forthcoming pattern matching language features that might affect the design of the API.
Appendix - Weather Forecast Example
The following example program issues a request to the U.S. National Weather Service REST API for a seven-day weather forecast for the lower Manhattan area of New York City. It receives a JSON document in the response body. The program then parses the document, navigates down a couple levels in the structure, and obtains an array of forecasts. The program continues by extracting the temperature from each forecast, averaging them, and printing the result.
import java.net.*;
import java.net.http.*;
import jdk.incubator.json.Json;
import jdk.incubator.json.JsonValue;
void main() throws Exception {
var query = "https://api.weather.gov/gridpoints/OKX/33,42/forecast";
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(query)).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
JsonValue json = Json.parse(response.body());
json.get("properties").get("periods").asList().stream()
.mapToInt(j -> j.get("temperature").asInt())
.average()
.ifPresent(IO::println);
}
Enabling the incubating API
The JSON API is an incubator module,
which is disabled by default. It must be enabled using a command-line
option --add-modules jdk.incubator.json that adds the incubator
module to the set of modules available for resolution. To run this
program, you must provide this option at run time and also at compile
time if appropriate.
To run the average forecast program as a single-file source code program, run the following:
java --add-modules jdk.incubator.json Weather.java
The output will be something like this. (Of course, the average temperature will likely differ.)
WARNING: Using incubator modules: jdk.incubator.json
53.357142857142854
To compile the program with javac and run it with java, run the following:
javac --add-modules jdk.incubator.json Weather.java
java --add-modules jdk.incubator.json Weather
It's possible to use jshell to experiment interactively with the
API. As before, it's necessary to supply a command line option to
enable the incubator module:
jshell --add-modules jdk.incubator.json
jshell> import jdk.incubator.json.*
jshell> Json.parse("""
...> { "name": "Today", "temperature": 54 }
...> """)
$2 ==> {"name":"Today","temperature":54}
jshell> $2.get("temperature").asInt()
$3 ==> 54