# json-simple, a small Java JSON toolkit that still refuses to change

> fangyidong/json-simple is a dependency-free JSON library for Java with a 27 KB jar, a strict mapping of JSON types to Java types, and a SAX-style streaming parser for large documents. After a decade without a release, version 1.1.2 was prepared with a compatibility harness that compares every change against the previous release, because a small library that other people's code depends on cannot simply be fixed.

**fangyidong/json-simple** — A simple Java toolkit for JSON. You can use json-simple to encode or decode JSON text.

- Repository: https://github.com/fangyidong/json-simple
- Stars: 761 · Forks: 339
- Language: Java
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/fangyidong-json-simple

## The library and the naming trap

JSON.simple is a small, dependency-free Java toolkit for encoding and decoding JSON, conforming closely but not strictly to RFC 8259, with a jar of about 27 KB that targets Java 8.

What it does is encode and decode java.util.Map and java.util.List directly, with no data binding, no annotations and no reflection. It ships as an OSGi bundle and declares the JPMS module name org.json.simple. It also includes a stoppable, SAX-style streaming parser for large documents, which is the feature that justifies the library existing alongside the standard library, since neither the JDK nor the usual alternatives give you a pull parser without a dependency.

The section the README puts first is called not to be confused with, and it is the most useful thing to read before you install anything. Three widely used Java libraries expose a class called JSONObject, and they are not compatible with one another. If code written against this one calls getString, getInt or optString, it is aimed at a different library, because no such methods exist here.

The table contrasts three. This one is com.googlecode.json-simple:json-simple and reads a value as (String) obj.get("k"). JSON-java is org.json:json and reads it as obj.getString("k"). And the json-simple rewrite by Clifton Labs is com.github.cliftonlabs:json-simple 2.x and later, which uses Jsoner.deserialize and a JsonObject of its own.

That is a genuinely valuable warning, because a dependency upgrade that swaps one json-simple for another changes every call site, and the failure is a compile error rather than a runtime one, which is at least the good kind.

The Maven coordinates are com.googlecode.json-simple:json-simple, with 1.1.1 as the current release on Maven Central and 1.1.2 described but not yet published. The project is Apache-2.0 licensed, on the master branch, with the last push on 2026-09-07.

## The type contract and the cast everyone gets wrong

The README calls the return-type contract the single most important thing to know about the library, and the decoding example shows the shape.

```java
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;

String text = "{\"name\":\"json-simple\",\"stars\":42,\"tags\":[\"java\",\"json\"]}";

JSONObject obj = (JSONObject) JSONValue.parseWithException(text);

String name = (String) obj.get("name");
Long stars  = (Long)   obj.get("stars");
JSONArray tags = (JSONArray) obj.get("tags");
String first = (String) tags.get(0);
```

The mapping is exact. An object becomes a JSONObject, which is a HashMap. An array becomes a JSONArray, which is an ArrayList. A string becomes a String. An integer number becomes a Long, never an Integer, whatever the magnitude. A fractional number becomes a Double, never a Float or a BigDecimal. true and false become a Boolean, and null becomes null.

The consequence is stated plainly: casting a small integer to Integer is the most common mistake made against this API, and it always throws ClassCastException. The advice is to use Long, or to call ((Number) value).intValue().

That is a real design decision with a real cost. A library that boxed every integer as Integer would be friendlier for the common case and would break on the uncommon one, and this one picks the other trade. It is defensible, because the alternative is a library whose behaviour depends on the magnitude of your data, but it means every consumer has to know the rule.

Two other details in that section catch people. JSONObject and JSONArray are raw HashMap and ArrayList subclasses in the 1.x line, so values come back as Object and need casting. And key order in a JSONObject is not preserved, which the README points at the container factories section for.

There is a third subtlety the README flags: because get returns null both for a missing key and for a key whose value is JSON null, you use containsKey when the difference matters.

## Why 1.1.2 exists and what the compatibility harness does

The account of the 1.1.2 release is the most interesting part of the README, and it explains a lot about how a mature small library has to be maintained.

The first reason is fixing real defects. Four bugs had been present for years: an integer too large for a long escaped as NumberFormatException instead of the documented ParseException; ItemList.getArray() threw ClassCastException for every input it was ever given; ItemList(String, String) used the input string as its separator; and a ParseException could not be serialized.

The second set is arguably more interesting, because these were regressions that had reached master after 1.1.1 shipped and would have broken existing users on upgrade: a widened parameter type that caused NoSuchMethodError in already-compiled callers, a swapped toString and getMessage, and a changed serialVersionUID. All are fixed, and each has a regression test.

That last detail, a changed serialVersionUID, is the sort of thing that breaks a long-running service that serialises exceptions across a boundary, and it got in because the project had no release for a decade and so no compatibility discipline.

The second reason for the release is about making the project easier to work on, and the README states the motivation in terms that apply to humans and AI assistants alike. A library this old carries knowledge that lived only in the maintainer's head, which is exactly what a newcomer gets wrong first. So the undocumented parts are now written down: the return-type contract, the parser's long-standing leniency, and the behaviours that look like bugs but are relied on downstream and must not be fixed.

The mechanisms are concrete. CLAUDE.md records those constraints. Yylex.java now says clearly that it is generated from doc/json.lex, which stops someone editing the generated lexer by hand. And tools/compat-check.sh compares every change against the last published release on four axes: API surface, binary compatibility, behaviour, and serialization. The stated point is that a wrong change now fails loudly instead of quietly shipping.

The README also says how the update was made, with the help of Claude Code and every change reviewed by hand before it landed, and that the compatibility harness exists partly for that reason, since generated changes need evidence rather than trust.

The parser leniency deserves a mention because it is the other half of the contract. The library conforms closely but not strictly to RFC 8259, and the leniency has been there long enough to be depended on. That is precisely why the README says the behaviours that look like bugs but are relied on downstream must not be fixed, and why the release is a careful one rather than a tidy-up.

## Where json-simple fits against the alternatives

The alternatives in Java are numerous and the choice is mostly about how much structure you want in the result.

The first is JSON-java, org.json:json, which reads values through typed accessors like obj.getString("k"). The README's table exists to make sure you do not accidentally substitute one for the other.

The case for json-simple is narrow and specific. A dependency-free jar of 27 KB that works on Java 8, in an environment where a dependency is a problem, is a real advantage. An OSGi bundle with a declared JPMS module name fits modular applications cleanly. And the streaming parser is available without pulling anything else in.

The cost is the type contract. Every value comes back as Object and needs a cast, the integer type is always Long, and key order is not preserved. That is more work at every call site than a typed library gives you, and the README is candid that this is what the library does rather than hiding it.

The version 1.1.2 situation is worth thinking about before you pick it. If you take 1.1.1 from Maven Central, you take the version with the four long-standing defects. If you build from master, you take the fixes and the compatibility harness, and the README says the changes were reviewed by hand. For a library this old with this many downstream users, the compatibility harness is arguably the more valuable contribution of the two.

## Conclusion

Adopt json-simple if you want JSON handling in a Java project with no dependencies and a small jar, and you are willing to cast every value to the documented type. Skip it if you want typed accessors, because the library deliberately returns plain Object from get and the README warns that code expecting getString or getInt is aimed at a different library with the same class name. Verify first by reading the return type table, because a small integer arriving as Long rather than Integer is the mistake that catches everyone.

## FAQ

### What Java types does json-simple return when parsing JSON?

An object becomes a JSONObject, which is a HashMap, an array becomes a JSONArray, an integer number becomes a Long and never an Integer whatever the magnitude, a fractional number becomes a Double, and true, false and null map to a Boolean and null. Casting a small integer to Integer always throws a ClassCastException, so use Long or ((Number) value).intValue().

### Which other Java JSON libraries should not be confused with json-simple?

Three libraries expose a class called JSONObject and are not compatible with each other. This one is com.googlecode.json-simple:json-simple and reads a value with (String) obj.get("k"), JSON-java is org.json:json and uses obj.getString("k"), and the Clifton Labs rewrite is com.github.cliftonlabs:json-simple 2.x and later, which uses Jsoner.deserialize.

### Why is there a json-simple release after so long without one?

The README says 1.1.2 exists for two reasons: to fix four real defects and three regressions that had reached master after 1.1.1 shipped, and to write down the undocumented contract so the project is easier to work on. The compatibility harness that compares changes against the last published release exists partly because generated changes need evidence rather than trust.

### Does json-simple have a streaming parser?

Yes, it includes a stoppable, SAX-style streaming parser for large documents, which is available without any additional dependency. The jar is about 27 KB, it ships as an OSGi bundle, and it declares the JPMS module name org.json.simple.

### How does json-simple compare with Jackson for a Java project?

Jackson offers data binding, while json-simple gives a dependency-free 27 KB jar that works on Java 8, but every value comes back as Object needing a cast, and key order in a JSONObject is not preserved.

## Sources

- [fangyidong/json-simple on GitHub](https://github.com/fangyidong/json-simple)
- [Issues](https://github.com/fangyidong/json-simple/issues)
- [License: Apache-2.0](https://github.com/fangyidong/json-simple/blob/master/LICENSE)
- [README](https://github.com/fangyidong/json-simple/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fangyidong-json-simple
