# JEP 540: Simple JSON API (Incubator)

## Summary

Define a simple, standard API for parsing and generating JSON documents so that doing so does not require an external library. Enable many JSON processing tasks to be accomplished with little coding. This is an [incubating API](https://openjdk.org/jeps/11).

## History

This JEP supersedes [JEP 198](https://openjdk.org/jeps/198), *Light-Weight JSON API*, which was written in 2014. Circumstances have changed in the intervening years, so here we take a different approach.

## Goals

- Provide a standard means in the Java Platform to process [RFC 8259](https://www.rfc-editor.org/info/rfc8259/) compliant JSON documents with low ceremony.

<!--THE END-->

- Keep the API small, simple, and easy to learn. Provide only those data types and operations required for strict conformance to RFC 8259, in order to facilitate machine-to-machine communication. Avoid features such as multiple parsing configurations, syntax extensions, data binding, and streaming.
- Ensure that code that navigates and extracts data from JSON documents with a known structure is simple and readable. Because JSON documents do not have schemas, such code serves as a *de facto* schema and should be readable as such.
- Enable easy and quick exploration of unfamiliar JSON documents. We often interact with JSON documents in an exploratory manner, writing code not using a specification but instead trying it out against example documents. The API should provide methods that fail fast with clear error messages, enabling quick exploration.
- Ensure that missing or unexpected values can be handled in a resilient fashion, since JSON document structures can evolve over time.
- Make the JDK itself capable of parsing and generating JSON documents.

## Non-Goals

- It is not a goal to create an API that supplants established external JSON libraries.

## Motivation

JSON is ubiquitous in modern computing. The Java ecosystem contains a wide range of established JSON libraries: [Jackson](https://github.com/FasterXML/jackson), [Gson](https://google.github.io/gson/), Jakarta JSON [Processing](https://jakarta.ee/specifications/jsonp/2.1/) and [Binding](https://jakarta.ee/specifications/jsonb/3.0/), [Fastjson 2](https://github.com/alibaba/fastjson2), and more. Not only do these libraries enable the parsing and generation of JSON documents, but they also support extended JSON syntaxes such as [JSON5](https://json5.org/) and include higher-level features such as data binding, i.e., converting Java objects to and from JSON with a high degree of customization, and event-based streaming.

We often, however, just need to perform simple tasks such as extracting some data from a JSON document. The Python or Go code to accomplish such tasks is simple; the Java code should be equally simple.

For example, consider the task of computing the average of a set of forecast temperatures in a response from the [U.S. National Weather Service REST API](https://www.weather.gov/documentation/services-web-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",
        ...
      },
      ...
    ]
  }
}
```

To compute the average forecast temperature requires parsing the document, navigating to the location in the structure that contains the forecasts, and iterating over the array of forecasts while extracting the temperature data. We should be able to tackle simple tasks like this with simple Java code, without installing an external library and without suspecting that another language might make us more productive.

A key goal driving the recent evolution of the Java Platform has been to enable simple tasks to be accomplished more easily and with less ceremony. Features serving this goal include [convenience factory methods for collections](https://openjdk.org/jeps/269), [`var` declarations](https://openjdk.org/jeps/286), [running programs from source files](https://openjdk.org/jeps/330), and [compact source files and instance main methods](https://openjdk.org/jeps/512). A simple JSON API for parsing and generating JSON documents would also serve this important goal.

### Using JSON in the JDK

A standard JSON API in the Java Platform would also pave the way for further use of JSON in the Platform and by the JDK itself, since the JDK cannot have external dependencies. One potential use case is configuration files. The JDK uses the [property file](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/Properties.html) format for various configuration files, such as [security properties files](https://docs.oracle.com/en/java/javase/26/security/security-properties-file.html). A weakness of this format is that it cannot express structured data. To represent an array in a property file, you must use clumsy workarounds such as sequentially numbered properties:

```
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
...
```

With JSON built into the JDK, configuration files could represent arrays naturally, using JSON arrays:

```
{
  "providers": [ "SUN", "SunRsaSign", "SunEC" ],
  ...
}
```

## Description

The [`jdk.incubator.json`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/package-summary.html) API is organized around the [`JsonValue`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html) interface, which represents a JSON value.

The JSON syntax has four kinds of primitives:

1. JSON strings, delimited with double quotes:
   
   ```
   "Hello"
   "My name is 'Bob'"
   "\u006a\u0061\u0076\u0061"
   ```
2. JSON numbers, represented in base 10 using decimal digits:
   
   ```
   6  6.0  31.84  2.9E+5
   ```
3. JSON boolean literals: `true` and `false`
4. The JSON null literal: `null`

and two kinds of structures:

5. JSON objects, 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 ] ]
   }
   ```
6. JSON arrays, delimited by `[` `]` and composed of comma-separated JSON values:
   
   ```
   [ 1, 2, 3, { "value": "4" }, [ 5, 6 ] ]
   ```

The `JsonValue` interface thus has six corresponding sub-interfaces: [`JsonString`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonString.html), [`JsonNumber`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNumber.html), [`JsonBoolean`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonBoolean.html), [`JsonNull`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNull.html), [`JsonObject`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonObject.html), and [`JsonArray`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonArray.html). Each interface declares operations appropriate to its corresponding JSON syntactic element: Instances of the primitive sub-interfaces offer conversions to Java primitives and strings, `JsonObject` instances expose members, and `JsonArray` instances expose array elements.

The `JsonValue` interface is [sealed](https://openjdk.org/jeps/409), which guarantees that any `JsonValue` instance is always one of this fixed set of subtypes and thus exhaustive `switch` expressions and statements do not require a `default` clause.

The JSON API makes it easy to parse JSON documents that conform to [RFC 8259](https://www.rfc-editor.org/info/rfc8259/). The [`parse`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/Json.html#parse%28java.lang.String%29) method of the [`Json`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/Json.html) class returns a tree of `JsonValue` instances that expose the names, types, and values of the parsed JSON data. Returning to the National Weather Service example, we can compute the average forecast temperature in just a few lines:

```
String body = ... REST response body, which is 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](#appendix).)

The API also makes it easy to generate JSON documents. For example, this code:

```
IO.println(JsonObject.of(Map.of("providers",
                                JsonArray.of(List.of(JsonString.of("SUN"),
                                                     JsonString.of("SunRsaSign"),
                                                     JsonString.of("SunEC"))))));
```

produces the output:

```
{"providers":["SUN","SunRsaSign","SunEC"]}
```

### Parsing and navigating JSON documents

The `Json` class can parse a JSON document contained in either a `String` or a `char` array. A JSON document might be a REST API response body read from the network, a configuration file read from disk, or some other text payload produced by an application.

Parsing a JSON document requires a single call to one of the [`Json.parse`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/Json.html#parse%28java.lang.String%29) methods:

```
JsonValue root = Json.parse(doc);
```

Parsing is strict: The document must conform to [RFC 8259](https://www.rfc-editor.org/info/rfc8259/). Syntax extensions such as trailing commas and comments are not supported. Additionally, documents must not have objects with duplicate member names. This policy, permitted by the RFC, provides maximum interoperability and predictability, and reduces concerns about processing malformed or ambiguous JSON documents. (See [below](#duplicates) for a full discussion.)

Successful parsing returns an instance of `JsonValue`. Unsuccessful parsing throws an unchecked [`JsonParseException`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonParseException.html). The exception includes a detail message that provides specific information about the error, its path from the root of the document, and its location within the document. For example, when a document contains duplicate member names in an object, the exception thrown has the form:

```
jdk.incubator.json.JsonParseException: The duplicate member name: "providers" was
already parsed. Path: "{". Location: line 2, position 4.
```

Most JSON documents have a JSON object or JSON array at the root. For example, a [JSON-formatted thread dump produced by the `jcmd` tool](https://download.java.net/java/early_access/jdk27/docs/api/jdk.management/com/sun/management/doc-files/threadDump.html) contains a root object:

```
{
  "threadDump": {
    "formatVersion": 2,
    "processId": 45178,
    "time": "2026-04-16T23:13:02.709630Z",
    "runtimeVersion": "27-internal",
    "threadContainers": [
      {
        "container": "<root>",
        "parent": null,
        "owner": null,
        "threads": [
          {
            "tid": 3,
            "time": "2026-04-16T23:13:02.906891Z",
            "name": "main",
            "state": "WAITING",
            ...
```

The root object contains a single member, the nested `threadDump` object, and `threadDump` itself contains both primitive and structural JSON values.

Once you have obtained the root `JsonValue` via `Json.parse(...)`, you can retrieve values from objects and arrays via their *access methods*, which return the requested member value or array element as a `JsonValue`. It is not necessary to downcast a `JsonValue` to `JsonObject` to access a member value, or to `JsonArray` to access an array element.

- [`get(String)`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#get%28java.lang.String%29) obtains the value of an object member. To obtain the thread dump object:
  
  ```
  JsonValue threadDump = root.get("threadDump");
  ```
- [`get(int)`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#get%28int%29) obtains an array element. To obtain the root thread container:
  
  ```
  JsonValue firstContainer = threadDump.get("threadContainers").get(0);
  ```

If the `JsonValue` instance is of the wrong type, or if the requested member or element does not exist, the access methods throw a [`JsonValueException`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValueException.html).

### Converting JSON values to Java values

You can convert a JSON value to a Java value by calling one of the *conversion methods* of the `JsonValue` interface. For a conversion to succeed, the `JsonValue` must be an instance of the appropriate subtype of `JsonValue`:

Subtype Method Resulting Java type [`JsonString`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonString.html) [`asString()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asString%28%29) `java.lang.String` [`JsonNumber`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNumber.html) [`asInt()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asInt%28%29) `int` [`JsonNumber`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNumber.html) [`asLong()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asLong%28%29) `long` [`JsonNumber`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNumber.html) [`asDouble()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asDouble%28%29) `double` [`JsonBoolean`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonBoolean.html) [`asBoolean()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asBoolean%28%29) `boolean` [`JsonObject`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonObject.html) [`asMap()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asMap%28%29) `java.util.Map` [`JsonArray`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonArray.html) [`asList()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asList%28%29) `java.util.List`

For example, you can retrieve the Java `String` value associated with the thread dump's "time" member:

```
JsonValue threadDumpTime = threadDump.get("time");
String time = threadDumpTime.asString();
```

You can convert the thread containers array into a `List` of `JsonValue` instances and process each instance:

```
threadDump.get("threadContainers").asList().forEach(jv -> ...);
```

You can access the thread dump object as a `Map` to retrieve the number of members:

```
int count = threadDump.asMap().size();
```

You can navigate deeply into a JSON document, chaining access methods and converting to a Java value only at the end. To retrieve the thread identifier value of the first thread in the root thread container:

```
long tid = threadDump.get("threadContainers").get(0)
                     .get("threads").get(0).get("tid").asLong();
```

The design of the conversion methods eliminates most `instanceof` checking and downcasting in cases where a specific JSON data type is expected in a document:

- [`asString()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asString%28%29) converts a `JsonString` instance into a Java `String` with RFC 8259 JSON escape sequences translated to their corresponding characters.
- [`asInt()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asInt%28%29) converts a `JsonNumber` instance to a Java `int` if its numeric value can be represented exactly.
- [`asLong()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asLong%28%29) converts a `JsonNumber` instance to a Java `long` if its numeric value can be represented exactly.
- [`asDouble()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asDouble%28%29) converts a `JsonNumber` instance to a Java `double` if its numeric value can be represented accurately.
- [`asBoolean()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asBoolean%28%29) converts a `JsonBoolean` instance to a Java `boolean` value of `true` or `false`.
- [`asMap()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asMap%28%29) converts a `JsonObject` instance into an unmodifiable Java `Map`. If the JSON object contains no members, an empty `Map` is returned.
- [`asList()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asList%28%29) converts a `JsonArray` instance into an unmodifiable Java `List`. If the JSON array contains no elements, an empty `List` is returned.

There is no conversion method for the JSON null value. [`JsonNull`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonNull.html) instances can be handled by testing for `instanceof JsonNull` or via the [`tryValue`](#Handling-null-values) method.

If a `JsonValue` is not an instance of the appropriate subtype for a conversion method then the method throws a `JsonValueException`. 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 target Java numeric type, which also causes a `JsonValueException` to be thrown. See [below](#JSON-numbers) for a deeper discussion of number handling and conversions.

### Handling JSON document evolution

JSON documents from a particular source may evolve, over time, in ways that violate your previous expectations of their structure and content:

- You might call access methods expecting member names or array indices that do not exist in the JSON objects and JSON arrays of the document.
- You might call conversion methods applicable to one JSON type on values of a different type.

If you call access or conversion methods on the wrong type, they throw a `JsonValueException`. This exception is unchecked, so that scripts and small programs are easier to read and write.

Continuing with the thread dump example, recall that the root JSON value is a JSON object with a single member, `threadDump`. This code:

```
JsonValue name = root.get("threadName");
```

throws a `JsonValueException` with a message that makes it clear the JSON object does not contain a member with the name `"threadName"`, while this code:

```
List<JsonValue> threadDumpList = threadDump.asList();
```

throws a `JsonValueException`, whose message makes it clear the `threadDump` member is a JSON object, not a JSON array.

The exception message describes the path 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 extract the thread identifier incorrectly converted it to a `boolean` instead of a `long`:

```
boolean tid = threadDump.get("threadContainers").get(0)
                        .get("threads").get(0).get("tid").asBoolean();
```

then the exception thrown by `asBoolean()` would have the form:

```
jdk.incubator.json.JsonValueException: JsonNumber is not a JsonBoolean. Path:
 "{threadDump{threadContainers[0{threads[0{tid". Location: line 13, position 19.
```

### Handling optional members

If you do not know whether a JSON object has a member with a given name, you can use the [`tryGet`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#tryGet%28java.lang.String%29) access method. This returns an `Optional` instance containing the member's value, or else an empty `Optional` if the member does not exist. (The `get` method, by contrast, confirms that the member exists and throws an exception if it does not.) The `tryGet` method throws a `JsonValueException` if it is not called on a `JsonObject`.

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 must be prepared for the `waitingOn` member to be absent. You can handle this via `tryGet`:

```
JsonValue thread = ...
thread.tryGet("waitingOn")
      .ifPresent(result -> ...);
```

The lambda passed to `ifPresent` is called only if the `waitingOn` member is present.

### Handling null values

If you do not know whether a JSON value is a JSON null, you can use the [`tryValue`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#tryValue%28%29) access method. This method returns an empty `Optional` if the JSON value upon which it is invoked is a `JsonNull`; otherwise, it returns that value.

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 "&lt;root&gt;" 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 must be prepared for the parent member to be either a JSON string or a JSON null. You can handle this via `tryValue`:

```
JsonValue container = ...
container.get("parent").tryValue()
         .ifPresent(result -> ...);
```

The lambda passed to `ifPresent` is called only if the "parent" member's value is not a JSON null.

### Handling variable structure and content

The structure and content of JSON documents in a particular context is often uniform, but sometimes it is variable. It might vary across different sources, or over time from a particular source which itself evolves, or even within the same document.

For example, in thread dumps in JDK 26 and earlier releases, thread identifiers are represented as JSON strings; in [JDK 27 and later releases](https://bugs.openjdk.org/browse/JDK-8381002), thread identifiers are represented as JSON numbers.

Code that expects the `tid` to be a JSON number, for example:

```
long tid = thread.get("tid").asLong();
```

will fail with a `JsonValueException` if it encounters a thread dump produced by a version of the JDK that emits `tid` values as JSON strings.

In either representation, the numeric value is specified to fit in a Java `long`. You could use `instanceof` to check whether you have a `JsonNumber` or a `JsonString`, but it is clearer to use type patterns in 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

To generate a JSON document, in string form, from a `JsonValue`, simply invoke its [`toString`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#toString%28%29) method. This method returns a compact string representation in which 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}
```

(The `toString` method is distinct from the `asString` method, which throws an exception if the `JsonValue` upon which it is invoked is not a `JsonString`.)

The static method [`Json.toDisplayString`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/Json.html#toDisplayString%28jdk.incubator.json.JsonValue,int%29) emits a pretty-printed form of a JSON document, where members and elements are separated by newlines and nested structures are indented by a given amount. For example, this code:

```
IO.println(Json.toDisplayString(json, 2));
```

prints the above structure with two spaces of indentation:

```
{
  "service": "web_server",
  "id": 3
}
```

The outputs of both the `toString` and `Json.toDisplayString` methods are parsable by the `Json.parse` method, which will produce a `JsonValue` that is equivalent to the original.

### JSON numbers

The syntax for JSON numbers defined in [RFC 8259](https://www.rfc-editor.org/info/rfc8259/) can represent decimal values of arbitrary precision and range. The JSON API enables JSON numbers to be processed losslessly; in most applications, however, common numeric types suffice.

RFC 8259 advises that good interoperability among JSON libraries can be achieved by using [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) 64-bit binary floating point values, corresponding to the Java `double` type. The [`asDouble()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asDouble%28%29) method therefore 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, a `JsonValueException` is thrown. Infinity and not-a-number ("NaN") values are not representable in JSON, and thus are never returned. Negative zero, however, is representable in JSON, and thus may be returned.

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 frequently used, so the [`asInt()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asInt%28%29) 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; 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, not integral
int i4 = Json.parse("2147483648").asInt();  // fails, out of range
```

The conversion method [`asLong()`](https://cr.openjdk.org/~naoto/json/javadoc/api/jdk.incubator.json/jdk/incubator/json/JsonValue.html#asLong%28%29) 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 [primitive types in patterns](https://openjdk.org/jeps/530) (currently a preview feature), to perform a safe conversion. For example, if you expect a JSON number to be representable as a `short`:

```
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 numbers can have arbitrary precision and range. The `asDouble()`, `asInt()`, and `asLong()` methods, by definition, handle only a subset of JSON numeric values; they reject out-of-range values, and they round overly-precise values. To handle JSON numeric data without loss of information, you can convert essentially any JSON number to a [`java.math.BigDecimal`](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/math/BigDecimal.html) instance:

```
BigDecimal bd = new BigDecimal(jn.toString());
```

## Alternatives

- [*Provide a full feature set instead of a limited feature set.*]()
  
  [The many existing external JSON libraries provide, among them, a broad set of features. We cannot possibly include all of these features in the Java Platform; we must, instead, select a subset that provides the greatest value relative to its cost.  
  \
  We have excluded the commonly provided feature of data binding. This feature is undeniably useful and convenient for many applications. However, it would add a significant API footprint and increase implementation and maintenance costs dramatically. Many use cases do not require data binding, so we consider this feature not strictly necessary. That the Jackson and Jakarta JSON libraries factor their data binding features into separate modules is an implicit recognition that there are use cases that do not need data binding.  
  \
  A streaming API is clearly essential for certain narrow, specialized use cases, but it induces a fair amount of application complexity for even simple data extraction tasks. We have thus excluded this feature.  
  \
  Omitting data binding and streaming leaves us with a DOM-like approach in which JSON documents are parsed into trees of JSON-specific objects from which data can be extracted easily. The API is small, and it incurs correspondingly small implementation and maintenance costs. This satisfies the needs of a significant subset of JSON applications, from the simplest to the moderately complex.  
  \
  An application might start off using the Java Platform's JSON API but eventually grow to need features such as data binding or streaming, necessitating a migration to a richer API in an external library. We do not view this scenario as a failure, and it is not sufficient justification to include high-cost features such as JSON data binding and streaming in the Java Platform.]()
  
  [*Integrate an external JSON library.*  
  \
  We could integrate an external library into the Java Platform and the JDK, as a downstream fork. This would raise difficult issues over licensing and governance. There would be continual tension over changes flowing in both directions, arising from different criteria regarding specification quality, compatibility, release schedules, and so forth. (We have experienced this tension in the past, with various XML APIs.) 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, since JSON is already handled well by external libraries.*  
  \
  Doing nothing would not serve the larger goal of enabling simple tasks to be accomplished more easily and with less ceremony, especially for simple programs and for newcomers to the Java Platform.  
  \
  Adding any external dependency to an application incurs cost and adds risk. There are probably applications that could benefit from using JSON but that do not, because their maintainers wish to minimize cost and risk. Such applications would benefit from having a standard JSON API in the Java Platform.]()
- []()
  
  []()[*Allow duplicate member names within JSON objects.*]()
  
  [This has been a longstanding issue with JSON. Early specifications were underdetermined with respect to the handling of duplicate member names within a single JSON object. JSON libraries behaved inconsistently, or else provided application-settable options to select the policy for handling duplicate names.]()
  
  [Unfortunately, an object with duplicate names is fundamentally ambiguous. When the issue of duplicate names was discussed on the]() [ECMAScript Discussion List](https://esdiscuss.org/topic/json-duplicate-keys) in 2013, the 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](https://www.rfc-editor.org/info/rfc8259/#section-4):
  
  > 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 libraries. This can lead to hard-to-diagnose errors, security vulnerabilities, decreased interoperability, and general lack of robustness. This phenomenon is discussed in [RFC 9413](https://www.rfc-editor.org/info/rfc9413/), "Maintaining Robust Protocols".
  
  For these reasons, we have chosen a strict approach where duplicate names are unconditionally treated as errors. The strict approach gives high confidence in the correctness of parsed documents. We hope that the erroneous documents mentioned in the 2013 ECMAScript 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 syntax extensions.*
  
  There are several variants of JSON, e.g., [JSON5](https://json5.org/), that support comments or trailing commas within arrays and objects. These extensions are intended to facilitate the hand-editing of JSON documents.
  
  Given our focus on simplicity and machine-to-machine communication, we do not support such extensions. Doing so would enlarge the testing matrix, increase the possibility of interoperability errors, and increase the overall development and maintenance burden.
  
  A common workaround is to pre-process incoming extended-JSON documents before parsing them. For example, single-line comments on lines starting with `'#'` characters are easily removed prior to parsing:
  
  ```
  String jsonc = Files.readString(Path.of("file-with-comments.json"));
  String json = jsonc.replaceAll("(?m)^\\s*#.*$", "");
  JsonValue jv = Json.parse(json);
  ```
- *Provide additional conversion methods.*
  
  We could include additional conversion methods for added convenience, such as `asBigDecimal()`. Such a method would be trivial to implement. However, the fact that a method is simple to include does not by itself justify adding it to the API. Our goal is to provide a minimal API that applications can build on top of. It is also just as easy for an application to do `new BigDecimal(JsonNumber.toString())`.
  
  While we find that each extra method is perhaps one more convenience, it is also one more opinionated decision. Additionally, since the conversion methods are defined uniformly on `JsonValue`, our goal is to keep this set of methods small and focused, consistent with our intention to make this API easy to use. By providing the minimal set of required operations, we leave users of this API free to add on the extra layers they require, tailored to their specific needs.

## Testing

We will rigorously test the JSON API to ensure that only canonical forms of RFC 8259 JSON can be parsed and generated. This will help ensure that using the API will not result in inconsistencies when interacting with other JSON libraries. To accomplish this, we will not only add comprehensive unit tests to the JDK but also leverage the established [JSON Parsing Test Suite](https://github.com/nst/JSONTestSuite), which contains numerous edge-case inputs.

## Risks and Assumptions

- We assume that input JSON documents can fit in memory, as either a `String` or a `char` array. Given our tree-based model, if we were to allow JSON sources such as files or network connections, issues such as insufficient memory would be possible with large documents. This decision aligns with our minimalist design philosophy.
- A risk of this proposal is that this new API might end up being used in applications that are already using external JSON libraries, resulting in messiness and confusion. We believe this risk is outweighed by the benefits.
- 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 program issues a request to the]() [U.S. National Weather Service REST API](https://www.weather.gov/documentation/services-web-api) for a seven-day weather forecast for Santa Clara, CA. It receives a JSON document in the response body. The program then parses the document, navigates into the structure, and obtains an array of forecasts. It then extracts the temperature from each forecast, averages them, and prints 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/MTR/97,83/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, at present, an [incubator module](https://openjdk.org/jeps/11), disabled by default. To use it, you must enable it via the command-line option `--add-modules jdk.incubator.json`, which adds the incubator module to the set of modules available for resolution. To run the above example program, you must provide this option at both compile time and run time.

To run the average forecast program as a single-file source code program, do this:

```
$ java --add-modules jdk.incubator.json Weather.java
```

The output will be something like:

```
WARNING: Using incubator modules: jdk.incubator.json
53.357142857142854
```

To compile the program with `javac` and run it with `java`, do this:

```
$ javac --add-modules jdk.incubator.json Weather.java
$ java --add-modules jdk.incubator.json Weather
```

You can use `jshell` to experiment interactively with the API. As before, you must enable the incubator module on the command line:

```
$ 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
jshell>
```