Skip to content

Latest commit

 

History

History
268 lines (203 loc) · 9.18 KB

File metadata and controls

268 lines (203 loc) · 9.18 KB

java.util.json Backport for JDK 21

Early access to the unstable java.util.json API - taken from OpenJDK sandbox July 2025.

Back Port Project Goals

  • ✅Enable early adoption: Let developers try the unstable Java JSON patterns today on JDK 21+
  • ✅API compatibility over performance: Focus on matching the emerging "batteries included" API design rather than competing with existing JSON libraries on speed.
  • ✅Track upstream API: Match emerging API updates to be a potential "unofficial backport" if a final official solution ever lands.
  • ✅Host Examples / Counter Examples: Only if there is community interest.

Non-Goals

  • 🛑Performance competition: This backport is not intended to be the fastest JSON library. The JDK internal annotations that boost performance had to be removed.
  • 🛑Feature additions: No features beyond what's in the experimental upstream branches. Contributions of example code or internal improvements are welcome.
  • 🛑Production / API stability: Its an unstable API. It is currently only for educational or experimenal usage.
  • 🛑Advoocacy / Counter Advocacy: This repo is not an endorsement of the proposed API nor a rejection of other solutions. Please only use the official Java email lists to debate the API or the general topic.

Current Status

This code (as at July 2025) is derived from the official OpenJDK sandbox repository at commit d22dc2ba89789041c3908cdaafadc1dcf8882ebf (Mid July 2025 "Improve hash code spec wording").

The original proposal and design rationale can be found in the included PDF: Towards a JSON API for the JDK.pdf

Modifications

This is a simplified backport with the following changes from the original:

  • Replaced StableValue with double-checked locking pattern.
  • Removed value-based class annotations.
  • Compatible with JDK 21.

Security Considerations

⚠️ This unstable API contains undocumented security vulnerabilities. The compatibility test suite (documented below) includes crafted attack vectors that expose these issues:

  • Stack exhaustion attacks: Deeply nested JSON structures can trigger StackOverflowError, potentially leaving applications in an undefined state and enabling denial-of-service attacks
  • API contract violations: The Json.parse() method documentation only declares JsonParseException and NullPointerException, but malicious inputs can trigger undeclared exceptions

These vulnerabilities exist in the upstream OpenJDK sandbox implementation and are reported here for transparency.

Building

Requires JDK 21 or later. Build with Maven:

mvn clean compile
mvn package

License

Licensed under the GNU General Public License version 2 with Classpath exception. See LICENSE for details.

API Overview

The API provides immutable JSON value types:

  • JsonValue - Base type for all JSON values
  • JsonObject - JSON objects (key-value pairs)
  • JsonArray - JSON arrays
  • JsonString - JSON strings
  • JsonNumber - JSON numbers
  • JsonBoolean - JSON booleans (true/false)
  • JsonNull - JSON null

Parsing is done via the Json class:

JsonValue value = Json.parse(jsonString);

Type Conversion Utilities

The Json class provides bidirectional conversion between JsonValue objects and standard Java types:

Converting from Java Objects to JSON (fromUntyped)

// Convert standard Java collections to JsonValue
Map<String, Object> data = Map.of(
    "name", "John",
    "age", 30,
    "scores", List.of(85, 92, 78)
);
JsonValue json = Json.fromUntyped(data);

Converting from JSON to Java Objects (toUntyped)

// Convert JsonValue back to standard Java types
JsonValue parsed = Json.parse("{\"name\":\"John\",\"age\":30}");
Object data = Json.toUntyped(parsed);
// Returns a Map<String, Object> with standard Java types

The conversion mappings are:

  • JsonObjectMap<String, Object>
  • JsonArrayList<Object>
  • JsonStringString
  • JsonNumberNumber (Long, Double, BigInteger, or BigDecimal)
  • JsonBooleanBoolean
  • JsonNullnull

This is useful for:

  • Integrating with existing code that uses standard collections
  • Serializing/deserializing to formats that expect Java types
  • Working with frameworks that use reflection on standard types

Usage Examples

Record Mapping

A powerful feature is mapping between Java records and JSON:

// Domain model using records
record User(String name, String email, boolean active) {}
record Team(String teamName, List<User> members) {}

// Create a team with users
Team team = new Team("Engineering", List.of(
    new User("Alice", "alice@example.com", true),
    new User("Bob", "bob@example.com", false)
));

// Convert records to JSON
JsonValue teamJson = Json.fromUntyped(Map.of(
    "teamName", team.teamName(),
    "members", team.members().stream()
        .map(u -> Map.of(
            "name", u.name(),
            "email", u.email(),
            "active", u.active()
        ))
        .toList()
));

// Parse JSON back to records
JsonObject parsed = (JsonObject) Json.parse(teamJson.toString());
Team reconstructed = new Team(
    ((JsonString) parsed.members().get("teamName")).value(),
    ((JsonArray) parsed.members().get("members")).values().stream()
        .map(v -> {
            JsonObject member = (JsonObject) v;
            return new User(
                ((JsonString) member.members().get("name")).value(),
                ((JsonString) member.members().get("email")).value(),
                ((JsonBoolean) member.members().get("active")).value()
            );
        })
        .toList()
);

Building Complex JSON

Create structured JSON programmatically:

// Building a REST API response
JsonObject response = JsonObject.of(Map.of(
    "status", JsonString.of("success"),
    "data", JsonObject.of(Map.of(
        "user", JsonObject.of(Map.of(
            "id", JsonNumber.of(12345),
            "name", JsonString.of("John Doe"),
            "roles", JsonArray.of(List.of(
                JsonString.of("admin"),
                JsonString.of("user")
            ))
        )),
        "timestamp", JsonNumber.of(System.currentTimeMillis())
    )),
    "errors", JsonArray.of(List.of())
));

Stream Processing

Process JSON arrays efficiently with Java streams:

// Filter active users from a JSON array
JsonArray users = (JsonArray) Json.parse(jsonArrayString);
List<String> activeUserEmails = users.values().stream()
    .map(v -> (JsonObject) v)
    .filter(obj -> ((JsonBoolean) obj.members().get("active")).value())
    .map(obj -> ((JsonString) obj.members().get("email")).value())
    .toList();

Error Handling

Handle parsing errors gracefully:

try {
    JsonValue value = Json.parse(userInput);
    // Process valid JSON
} catch (JsonParseException e) {
    // Handle malformed JSON with line/column information
    System.err.println("Invalid JSON at line " + e.getLine() + 
                       ", column " + e.getColumn() + ": " + e.getMessage());
}

Pretty Printing

Format JSON for display:

JsonObject data = JsonObject.of(Map.of(
    "name", JsonString.of("Alice"),
    "scores", JsonArray.of(List.of(
        JsonNumber.of(85),
        JsonNumber.of(90),
        JsonNumber.of(95)
    ))
));

String formatted = Json.toDisplayString(data, 2);
// Output:
// {
//   "name": "Alice",
//   "scores": [
//     85,
//     90,
//     95
//   ]
// }

JSON Test Suite Compatibility

This backport includes a compatibility report tool that tests against the JSON Test Suite to track conformance with JSON standards.

Running the Compatibility Report

First, build the project and download the test suite:

# Build project and download test suite
./mvnw clean compile generate-test-resources -pl json-compatibility-suite

# Run human-readable report
./mvnw exec:java -pl json-compatibility-suite

# Run JSON output (dogfoods the API)
./mvnw exec:java -pl json-compatibility-suite -Dexec.args="--json"

Current Status

The implementation achieves 99.3% overall conformance with the JSON Test Suite:

  • Valid JSON: 97.9% success rate (93/95 files pass)
  • Invalid JSON: 100% success rate (correctly rejects all invalid JSON)
  • Implementation-defined: Handles 35 edge cases per implementation choice (27 accepted, 8 rejected)

The 2 failing cases involve duplicate object keys, which this implementation rejects (stricter than required by the JSON specification). This is an implementation choice that prioritizes data integrity over permissiveness.

Understanding the Results

  • Files skipped: Currently 0 files skipped due to robust encoding detection that handles various character encodings
  • StackOverflowError: Security vulnerability exposed by malicious deeply nested structures - can leave applications in undefined state
  • Duplicate keys: Implementation choice to reject for data integrity (2 files fail for this reason)

This tool reports status rather than making API design decisions, aligning with the project's goal of tracking upstream development without advocacy.