docs
This documentation has been enhanced for AI coding agents with comprehensive examples, complete API signatures, thread safety notes, error handling patterns, and production-ready usage patterns.
Package: org.springframework.boot.json
Module: org.springframework.boot:spring-boot
Since: 1.0.0
Spring Boot provides abstraction over multiple JSON parsing libraries with a unified API. This allows applications to parse JSON without depending on specific implementations, with automatic detection and selection of available JSON libraries.
The JSON support system provides:
package org.springframework.boot.json;
import java.util.List;
import java.util.Map;
/**
* Interface to parse JSON into Map or List.
*
* Thread Safety: Implementations should be thread-safe.
*
* @since 1.0.0
*/
public interface JsonParser {
/**
* Parse the specified JSON string into a Map.
*
* @param json the JSON to parse (may be null)
* @return the parsed JSON as a map
* @throws JsonParseException if the JSON cannot be parsed
*/
Map<String, Object> parseMap(@Nullable String json);
/**
* Parse the specified JSON string into a List.
*
* @param json the JSON to parse (may be null)
* @return the parsed JSON as a list
* @throws JsonParseException if the JSON cannot be parsed
*/
List<Object> parseList(@Nullable String json);
}Uses Jackson library (preferred, most feature-rich).
package org.springframework.boot.json;
/**
* JsonParser implementation using Jackson.
* Requires tools.jackson.databind:jackson-databind on classpath (Jackson 3.x).
*
* Thread Safety: Thread-safe.
*
* @since 1.0.0
*/
public class JacksonJsonParser extends AbstractJsonParser {
/**
* Creates a JacksonJsonParser using the default JsonMapper configuration.
*/
public JacksonJsonParser() {
// Uses default JsonMapper internally
}
/**
* Creates a JacksonJsonParser with a custom JsonMapper.
* Allows full control over Jackson configuration (modules, features, etc.).
*
* @param jsonMapper the JsonMapper to use for parsing
* @since 1.0.0
*/
public JacksonJsonParser(JsonMapper jsonMapper) {
// Uses provided JsonMapper for parsing
}
}Uses Google's Gson library.
package org.springframework.boot.json;
/**
* JsonParser implementation using Gson.
* Requires com.google.code.gson:gson on classpath.
*
* Thread Safety: Thread-safe.
*
* @since 1.2.0
*/
public class GsonJsonParser extends AbstractJsonParser {
public GsonJsonParser() {
// Uses Gson internally
}
}Minimal built-in parser requiring no dependencies.
package org.springframework.boot.json;
/**
* Basic JSON parser using only JDK classes.
* No external dependencies required.
* Limited feature set - handles simple JSON only.
*
* Thread Safety: Thread-safe.
*
* @since 1.2.0
*/
public class BasicJsonParser extends AbstractJsonParser {
public BasicJsonParser() {
// No dependencies
}
}Abstract base class for JsonParser implementations providing common parsing utilities and error handling.
package org.springframework.boot.json;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.springframework.lang.Nullable;
/**
* Abstract base class for JsonParser implementations.
* Provides common utility methods for parsing JSON strings into Maps and Lists,
* with consistent null handling and error wrapping.
*
* Thread Safety: Thread-safe when subclass implementations are thread-safe.
*
* @since 2.0.1
*/
public abstract class AbstractJsonParser implements JsonParser {
/**
* Parse a JSON string into a Map using the provided parser function.
* Handles null input and trims whitespace before parsing.
*
* @param json the JSON string to parse (may be null)
* @param parser the function to parse the trimmed JSON string
* @return the parsed Map, or an empty Map if json is null or empty
*/
protected final Map<String, Object> parseMap(
@Nullable String json,
Function<String, Map<String, Object>> parser) {
// Implementation delegates to subclass parser function
return null; // Placeholder
}
/**
* Parse a JSON string into a List using the provided parser function.
* Handles null input and trims whitespace before parsing.
*
* @param json the JSON string to parse (may be null)
* @param parser the function to parse the trimmed JSON string
* @return the parsed List, or an empty List if json is null or empty
*/
protected final List<Object> parseList(
@Nullable String json,
Function<String, List<Object>> parser) {
// Implementation delegates to subclass parser function
return null; // Placeholder
}
/**
* Trim whitespace and optional prefix from JSON string before parsing.
* Useful for handling JSON with BOM or other prefixes.
*
* @param json the JSON string to parse (may be null)
* @param prefix optional prefix to remove (may be null)
* @param parser the function to parse the processed JSON string
* @param <T> the return type
* @return the parsed result
*/
protected final <T> T trimParse(
@Nullable String json,
String prefix,
Function<String, T> parser) {
// Implementation trims and removes prefix before delegating to parser
return null; // Placeholder
}
/**
* Try to parse JSON and wrap any checked exceptions as JsonParseException.
* Provides consistent error handling across all parser implementations.
*
* @param parser the parsing operation to attempt
* @param check the exception type to catch and wrap
* @param <T> the return type
* @return the parsed result
* @throws JsonParseException if parsing fails
*/
protected final <T> T tryParse(
Callable<T> parser,
Class<? extends Exception> check) {
// Implementation tries parsing and wraps exceptions
return null; // Placeholder
}
}Usage Example - Extending AbstractJsonParser:
import org.springframework.boot.json.AbstractJsonParser;
import org.springframework.boot.json.JsonParseException;
import java.util.List;
import java.util.Map;
import com.example.CustomJsonLibrary;
/**
* Custom JSON parser implementation using a third-party library.
*/
public class CustomJsonParser extends AbstractJsonParser {
private final CustomJsonLibrary library = new CustomJsonLibrary();
@Override
public Map<String, Object> parseMap(String json) {
// Use AbstractJsonParser utility for consistent null handling
return parseMap(json, (trimmedJson) -> {
try {
return library.parseToMap(trimmedJson);
} catch (Exception ex) {
throw new JsonParseException(ex);
}
});
}
@Override
public List<Object> parseList(String json) {
// Use AbstractJsonParser utility for consistent null handling
return parseList(json, (trimmedJson) -> {
try {
return library.parseToList(trimmedJson);
} catch (Exception ex) {
throw new JsonParseException(ex);
}
});
}
}Factory class for obtaining the most appropriate JsonParser implementation available on the classpath. Automatically detects and returns the best available parser.
package org.springframework.boot.json;
/**
* Factory to create a JsonParser.
* Tries Jackson, then Gson, then falls back to BasicJsonParser.
*
* @since 1.0.0
*/
public abstract class JsonParserFactory {
/**
* Static factory for the "best" JSON parser available on the classpath.
* Detection order:
* 1. Jackson (tools.jackson.databind.ObjectMapper)
* 2. Gson (com.google.gson.Gson)
* 3. BasicJsonParser (fallback, no dependencies)
*
* @return a JsonParser instance
*/
public static JsonParser getJsonParser();
}The factory uses classpath detection to select the parser:
tools.jackson.databind.ObjectMapper is presentcom.google.gson.Gson is presentimport org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;
// Example 1: Simple usage
public class SimpleParserUsage {
public void parseJson(String json) {
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> result = parser.parseMap(json);
System.out.println("Parser type: " + parser.getClass().getSimpleName());
}
}
// Example 2: Singleton pattern
@Component
public class JsonService {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Map<String, Object> parse(String json) {
return parser.parseMap(json);
}
public List<Object> parseArray(String json) {
return parser.parseList(json);
}
}
// Example 3: Check which parser is used
public class ParserDetection {
public static void main(String[] args) {
JsonParser parser = JsonParserFactory.getJsonParser();
if (parser instanceof JacksonJsonParser) {
System.out.println("Using Jackson parser (best performance)");
} else if (parser instanceof GsonJsonParser) {
System.out.println("Using Gson parser");
} else if (parser instanceof BasicJsonParser) {
System.out.println("Using BasicJsonParser (fallback)");
}
}
}Best Practice: Create a single JsonParser instance via JsonParserFactory.getJsonParser() and reuse it throughout your application. All implementations are thread-safe.
Runtime exception thrown when JSON parsing fails.
package org.springframework.boot.json;
/**
* Exception thrown when JSON parsing fails.
* Wraps underlying parsing exceptions from various JSON libraries
* providing a consistent exception type across all parser implementations.
*
* Thread Safety: Thread-safe (immutable after construction).
*
* @since 2.0.1
*/
public class JsonParseException extends IllegalArgumentException {
/**
* Creates a new JsonParseException with default message.
*/
public JsonParseException() {
this(null);
}
/**
* Creates a new JsonParseException with the specified cause.
* The detail message will be "Cannot parse JSON".
*
* @param cause the underlying cause (may be null)
*/
public JsonParseException(@Nullable Throwable cause) {
super("Cannot parse JSON", cause);
}
}Usage Example:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.Map;
public class ErrorHandlingExample {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Map<String, Object> parseWithFallback(String json, Map<String, Object> fallback) {
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
System.err.println("JSON parsing failed: " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
return fallback;
}
}
public void parseStrict(String json) {
try {
Map<String, Object> result = parser.parseMap(json);
processResult(result);
} catch (JsonParseException e) {
// Re-throw with more context
throw new IllegalArgumentException(
"Invalid JSON configuration: " + json.substring(0, Math.min(50, json.length())),
e
);
}
}
private void processResult(Map<String, Object> result) {
// Process parsed result
}
}Spring Boot provides interfaces for writing JSON output without requiring a full marshalling library. The JsonWriter and WritableJson interfaces enable lightweight JSON generation with configurable member mapping.
Interface for writing JSON output. Typically used when dependencies on fully-featured marshalling libraries (like Jackson or Gson) cannot be assumed.
package org.springframework.boot.json;
import java.io.IOException;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
/**
* Interface that can be used to write JSON output.
*
* Thread Safety: Implementations are typically thread-safe.
*
* @param <T> the type being written
* @since 3.4.0
*/
@FunctionalInterface
public interface JsonWriter<T> {
/**
* Write the given instance to the provided {@link Appendable}.
*
* @param instance the instance to write (may be {@code null})
* @param out the output that should receive the JSON
* @throws IOException on IO error
*/
void write(@Nullable T instance, Appendable out) throws IOException;
/**
* Write the given instance to a JSON string.
*
* @param instance the instance to write (may be {@code null})
* @return the JSON string
*/
default String writeToString(@Nullable T instance);
/**
* Provide a {@link WritableJson} implementation that may be used to write
* the given instance to various outputs.
*
* @param instance the instance to write (may be {@code null})
* @return a {@link WritableJson} instance that may be used to write the JSON
*/
default WritableJson write(@Nullable T instance);
/**
* Return a new {@link JsonWriter} instance that appends a new line after
* the JSON has been written.
*
* @return a new {@link JsonWriter} instance that appends a new line
*/
default JsonWriter<T> withNewLineAtEnd();
/**
* Return a new {@link JsonWriter} instance that appends the given suffix
* after the JSON has been written.
*
* @param suffix the suffix to write, if any
* @return a new {@link JsonWriter} instance that appends a suffix
*/
default JsonWriter<T> withSuffix(@Nullable String suffix);
/**
* Factory method to return a {@link JsonWriter} for standard Java types.
* Supports String, Number, Boolean, Collection, Array, Map, and WritableJson.
*
* @param <T> the type to write
* @return a {@link JsonWriter} instance
*/
static <T> JsonWriter<T> standard();
/**
* Factory method to return a {@link JsonWriter} with specific member mapping.
*
* @param <T> the type to write
* @param members a consumer which should configure the members
* @return a {@link JsonWriter} instance
* @see Members
*/
static <T> JsonWriter<T> of(Consumer<Members<T>> members);
}Key Features:
Interface representing JSON content that can be written to various outputs.
package org.springframework.boot.json;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import org.springframework.core.io.WritableResource;
/**
* JSON content that can be written out.
*
* Thread Safety: Implementations should be stateless and thread-safe.
*
* @since 3.4.0
* @see JsonWriter
*/
@FunctionalInterface
public interface WritableJson {
/**
* Write the JSON to the provided {@link Appendable}.
*
* @param out the {@link Appendable} to receive the JSON
* @throws IOException on IO error
*/
void to(Appendable out) throws IOException;
/**
* Write the JSON to a {@link String}.
*
* @return the JSON string
*/
default String toJsonString();
/**
* Write the JSON to a UTF-8 encoded byte array.
*
* @return the JSON bytes
*/
default byte[] toByteArray();
/**
* Write the JSON to a byte array with specified charset.
*
* @param charset the charset
* @return the JSON bytes
*/
default byte[] toByteArray(Charset charset);
/**
* Write the JSON to the provided {@link WritableResource} using UTF-8 encoding.
*
* @param out the {@link WritableResource} to receive the JSON
* @throws IOException on IO error
*/
default void toResource(WritableResource out) throws IOException;
/**
* Write the JSON to the provided {@link WritableResource} using given charset.
*
* @param out the {@link WritableResource} to receive the JSON
* @param charset the charset to use
* @throws IOException on IO error
*/
default void toResource(WritableResource out, Charset charset) throws IOException;
/**
* Write the JSON to the provided {@link OutputStream} using UTF-8 encoding.
* The output stream will not be closed.
*
* @param out the {@link OutputStream} to receive the JSON
* @throws IOException on IO error
*/
default void toOutputStream(OutputStream out) throws IOException;
/**
* Write the JSON to the provided {@link OutputStream} using given charset.
* The output stream will not be closed.
*
* @param out the {@link OutputStream} to receive the JSON
* @param charset the charset to use
* @throws IOException on IO error
*/
default void toOutputStream(OutputStream out, Charset charset) throws IOException;
/**
* Write the JSON to the provided {@link Writer}.
* The writer will be flushed but not closed.
*
* @param out the {@link Writer} to receive the JSON
* @throws IOException on IO error
*/
default void toWriter(Writer out) throws IOException;
/**
* Factory method to create a {@link WritableJson} with sensible toString().
*
* @param writableJson the source {@link WritableJson}
* @return a new {@link WritableJson} with sensible {@link Object#toString()}
*/
static WritableJson of(WritableJson writableJson);
}The JsonWriter.Members class provides a fluent API for configuring JSON member mapping.
Common Methods:
add(String name, Extractor<T, V> extractor) - Add named member with value extractionadd(String name) - Add member with access to full instancefrom(Extractor<T, V> extractor).usingMembers(...) - Configure nested membersaddMapEntries(Extractor<T, Map<K,V>> extractor) - Add all map entries to JSONMember.whenNotNull() - Only include member when value is not nullMember.as(Extractor<T, R> adapter) - Transform value to different typeMember.when(Predicate<T> predicate) - Conditionally include memberpackage com.example.json;
import org.springframework.boot.json.JsonWriter;
import org.springframework.boot.json.WritableJson;
import java.util.Map;
import java.util.List;
/**
* Writing standard Java types to JSON.
*/
public class StandardJsonWriting {
public void writeStandardTypes() throws Exception {
// Create writer for standard types
JsonWriter<Map<String, Object>> writer = JsonWriter.standard();
// Write a map to string
Map<String, Object> data = Map.of(
"name", "Spring Boot",
"version", "4.0.0",
"active", true,
"components", List.of("core", "web", "data")
);
String json = writer.writeToString(data);
System.out.println(json);
// Output: {"name":"Spring Boot","version":"4.0.0","active":true,"components":["core","web","data"]}
// Write to appendable
StringBuilder sb = new StringBuilder();
writer.write(data, sb);
// Get WritableJson for multiple outputs
WritableJson writable = writer.write(data);
byte[] bytes = writable.toByteArray();
String str = writable.toJsonString();
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* Custom object mapping with member configuration.
*/
public class CustomObjectMapping {
record Person(String firstName, String lastName, LocalDate dateOfBirth, String email) {}
public void writeCustomObject() {
// Configure custom member mapping
JsonWriter<Person> writer = JsonWriter.of((members) -> {
members.add("first", Person::firstName);
members.add("last", Person::lastName);
members.add("dob", Person::dateOfBirth)
.whenNotNull()
.as(DateTimeFormatter.ISO_DATE::format);
members.add("email", Person::email)
.whenNotNull();
});
Person person = new Person("John", "Doe", LocalDate.of(1990, 5, 15), "john@example.com");
String json = writer.writeToString(person);
System.out.println(json);
// Output: {"first":"John","last":"Doe","dob":"1990-05-15","email":"john@example.com"}
// Null handling
Person personWithoutEmail = new Person("Jane", "Smith", null, null);
String json2 = writer.writeToString(personWithoutEmail);
System.out.println(json2);
// Output: {"first":"Jane","last":"Smith"}
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import java.util.List;
/**
* Writing nested objects with member configuration.
*/
public class NestedObjectWriting {
record Address(String street, String city, String country) {}
record User(String name, Address address, List<String> tags) {}
public void writeNestedObjects() {
JsonWriter<User> writer = JsonWriter.of((members) -> {
members.add("name", User::name);
// Nested object with its own member configuration
members.add("address", User::address)
.whenNotNull()
.usingMembers((addressMembers) -> {
addressMembers.add("street", Address::street);
addressMembers.add("city", Address::city);
addressMembers.add("country", Address::country);
});
// Array member
members.add("tags", User::tags)
.whenNotEmpty();
});
User user = new User(
"Alice",
new Address("123 Main St", "Springfield", "USA"),
List.of("admin", "developer")
);
String json = writer.writeToString(user);
System.out.println(json);
// Output: {"name":"Alice","address":{"street":"123 Main St","city":"Springfield","country":"USA"},"tags":["admin","developer"]}
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import java.util.Map;
/**
* Writing map entries as JSON pairs.
*/
public class MapEntriesWriting {
record Event(String name, Map<String, String> labels) {}
public void writeMapEntries() {
// Add map entries as named object
JsonWriter<Event> writer1 = JsonWriter.of((members) -> {
members.add("name", Event::name);
members.add("labels", Event::labels)
.usingPairs(Map::forEach);
});
Event event = new Event("deployment", Map.of("env", "prod", "version", "1.0"));
String json1 = writer1.writeToString(event);
System.out.println(json1);
// Output: {"name":"deployment","labels":{"env":"prod","version":"1.0"}}
// Add map entries to existing JSON object (unnamed member)
JsonWriter<Event> writer2 = JsonWriter.of((members) -> {
members.add("name", Event::name);
members.from(Event::labels)
.usingPairs(Map::forEach); // No name, merges into parent object
});
String json2 = writer2.writeToString(event);
System.out.println(json2);
// Output: {"name":"deployment","env":"prod","version":"1.0"}
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import java.util.Map;
/**
* Writing JSON with suffixes (e.g., newlines).
*/
public class JsonWritingWithSuffix {
public void writeLinesWithNewline() {
// Add newline after each JSON object (useful for JSONL format)
JsonWriter<Map<String, Object>> writer = JsonWriter.<Map<String, Object>>standard()
.withNewLineAtEnd();
Map<String, Object> record1 = Map.of("id", 1, "name", "Alice");
Map<String, Object> record2 = Map.of("id", 2, "name", "Bob");
String line1 = writer.writeToString(record1);
String line2 = writer.writeToString(record2);
System.out.println(line1); // {"id":1,"name":"Alice"}\n
System.out.println(line2); // {"id":2,"name":"Bob"}\n
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import org.springframework.boot.json.WritableJson;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.WritableResource;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* Using WritableJson for various output types.
*/
public class WritableJsonOutputs {
public void writeToVariousOutputs() throws IOException {
JsonWriter<Map<String, Object>> writer = JsonWriter.standard();
Map<String, Object> data = Map.of("status", "ok", "code", 200);
WritableJson writable = writer.write(data);
// Write to String
String jsonString = writable.toJsonString();
System.out.println("String: " + jsonString);
// Write to byte array (UTF-8)
byte[] bytes = writable.toByteArray();
System.out.println("Bytes: " + bytes.length);
// Write to byte array (custom charset)
byte[] bytesUtf16 = writable.toByteArray(StandardCharsets.UTF_16);
// Write to OutputStream
try (FileOutputStream fos = new FileOutputStream("/tmp/output.json")) {
writable.toOutputStream(fos);
}
// Write to WritableResource
WritableResource resource = new FileSystemResource("/tmp/data.json");
writable.toResource(resource);
// Write to Writer
try (java.io.FileWriter fw = new java.io.FileWriter("/tmp/writer.json")) {
writable.toWriter(fw);
}
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
/**
* Conditional member inclusion based on predicates.
*/
public class ConditionalMemberInclusion {
record Product(String name, Double price, Integer stock, String description) {}
public void writeWithConditions() {
JsonWriter<Product> writer = JsonWriter.of((members) -> {
members.add("name", Product::name);
// Only include price if not null
members.add("price", Product::price)
.whenNotNull();
// Only include stock if greater than zero
members.add("stock", Product::stock)
.when(stock -> stock != null && stock > 0);
// Only include description if it has content
members.add("description", Product::description)
.whenHasLength();
});
// All fields present
Product p1 = new Product("Laptop", 999.99, 5, "High-performance laptop");
System.out.println(writer.writeToString(p1));
// Output: {"name":"Laptop","price":999.99,"stock":5,"description":"High-performance laptop"}
// Some fields missing
Product p2 = new Product("Mouse", null, 0, "");
System.out.println(writer.writeToString(p2));
// Output: {"name":"Mouse"}
}
}package com.example.json;
import org.springframework.boot.json.JsonWriter;
import java.time.Instant;
import java.util.Locale;
/**
* Transforming values before writing to JSON.
*/
public class ValueTransformation {
record LogEntry(String message, Instant timestamp, String level) {}
public void writeWithTransformation() {
JsonWriter<LogEntry> writer = JsonWriter.of((members) -> {
members.add("message", LogEntry::message);
// Transform Instant to ISO-8601 string
members.add("timestamp", LogEntry::timestamp)
.as(Instant::toString);
// Transform level to uppercase
members.add("level", LogEntry::level)
.as(level -> level.toUpperCase(Locale.ROOT));
});
LogEntry entry = new LogEntry(
"Application started",
Instant.parse("2024-01-15T10:30:00Z"),
"info"
);
String json = writer.writeToString(entry);
System.out.println(json);
// Output: {"message":"Application started","timestamp":"2024-01-15T10:30:00Z","level":"INFO"}
}
}Thread Safety: JsonWriter instances created via factory methods are thread-safe and can be reused across multiple threads. The WritableJson interface is typically implemented as stateless and thread-safe.
Performance: The JsonWriter implementation is optimized for lightweight JSON generation. For complex object graphs with circular references or advanced features, consider using a full JSON library like Jackson.
Best Practices:
JsonWriter instances once and reuse them (they are thread-safe)whenNotNull() and whenHasLength() to avoid writing empty fieldsas() for type transformations rather than manual conversionWritableJson when writing to multiple outputswithNewLineAtEnd() for JSONL formatpackage com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;
import java.util.List;
/**
* Automatic parser selection based on classpath.
*/
public class JsonParsingExample {
public void parseWithAutoDetection() {
// Automatically selects best available parser:
// 1. JacksonJsonParser (if Jackson present)
// 2. GsonJsonParser (if Gson present)
// 3. BasicJsonParser (always available as fallback)
JsonParser parser = JsonParserFactory.getJsonParser();
// Parse JSON object
String jsonObject = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}";
Map<String, Object> map = parser.parseMap(jsonObject);
System.out.println("Name: " + map.get("name"));
System.out.println("Age: " + map.get("age"));
System.out.println("City: " + map.get("city"));
// Parse JSON array
String jsonArray = "[\"apple\",\"banana\",\"cherry\"]";
List<Object> list = parser.parseList(jsonArray);
System.out.println("Fruits: " + list);
}
}package com.example.json;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.GsonJsonParser;
import org.springframework.boot.json.BasicJsonParser;
/**
* Explicitly choosing JSON parser implementation.
*/
public class ExplicitParserExample {
public void useJackson() {
JacksonJsonParser parser = new JacksonJsonParser();
String json = "{\"id\":1,\"active\":true}";
Map<String, Object> data = parser.parseMap(json);
System.out.println("ID: " + data.get("id"));
System.out.println("Active: " + data.get("active"));
}
public void useGson() {
GsonJsonParser parser = new GsonJsonParser();
String json = "[{\"id\":1},{\"id\":2}]";
List<Object> items = parser.parseList(json);
System.out.println("Items: " + items.size());
}
public void useBasic() {
// No external dependencies needed
BasicJsonParser parser = new BasicJsonParser();
String json = "{\"message\":\"Hello\"}";
Map<String, Object> result = parser.parseMap(json);
System.out.println("Message: " + result.get("message"));
}
}package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.List;
import java.util.Map;
/**
* Parsing nested JSON structures.
*/
public class ComplexJsonExample {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public void parseNestedObject() {
String json = """
{
"user": {
"id": 123,
"name": "John Doe",
"address": {
"street": "123 Main St",
"city": "Springfield"
}
}
}
""";
Map<String, Object> root = parser.parseMap(json);
@SuppressWarnings("unchecked")
Map<String, Object> user = (Map<String, Object>) root.get("user");
System.out.println("User ID: " + user.get("id"));
System.out.println("User Name: " + user.get("name"));
@SuppressWarnings("unchecked")
Map<String, Object> address = (Map<String, Object>) user.get("address");
System.out.println("City: " + address.get("city"));
}
public void parseArrayOfObjects() {
String json = """
[
{"id": 1, "name": "Product A", "price": 29.99},
{"id": 2, "name": "Product B", "price": 39.99},
{"id": 3, "name": "Product C", "price": 49.99}
]
""";
List<Object> products = parser.parseList(json);
for (Object item : products) {
@SuppressWarnings("unchecked")
Map<String, Object> product = (Map<String, Object>) item;
System.out.printf("Product %d: %s - $%.2f%n",
product.get("id"),
product.get("name"),
product.get("price"));
}
}
}package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParseException;
import org.springframework.boot.json.JsonParserFactory;
/**
* Proper error handling for JSON parsing.
*/
public class ErrorHandlingExample {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Map<String, Object> parseWithErrorHandling(String json) {
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
System.err.println("Failed to parse JSON: " + e.getMessage());
System.err.println("Invalid JSON: " + json);
// Return empty map or rethrow
return Map.of();
}
}
public void validateAndParse(String json) {
if (json == null || json.trim().isEmpty()) {
throw new IllegalArgumentException("JSON string cannot be null or empty");
}
try {
Map<String, Object> result = parser.parseMap(json);
processResult(result);
} catch (JsonParseException e) {
// Log error with context
System.err.printf("JSON parse error at position %d: %s%n",
findErrorPosition(json, e),
e.getMessage());
throw e;
}
}
private int findErrorPosition(String json, JsonParseException e) {
// Extract position from exception message if available
return 0;
}
private void processResult(Map<String, Object> result) {
// Process parsed JSON
}
}package com.example.json;
import org.junit.jupiter.api.Test;
import org.springframework.boot.json.*;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
/**
* Tests ensuring consistent behavior across parsers.
*/
class JsonParserTest {
private static final String JSON = "{\"key\":\"value\",\"number\":42}";
@Test
void jacksonParsesCorrectly() {
JsonParser parser = new JacksonJsonParser();
Map<String, Object> result = parser.parseMap(JSON);
assertThat(result)
.containsEntry("key", "value")
.containsEntry("number", 42);
}
@Test
void gsonParsesCorrectly() {
JsonParser parser = new GsonJsonParser();
Map<String, Object> result = parser.parseMap(JSON);
assertThat(result)
.containsEntry("key", "value")
.containsEntry("number", 42.0); // Gson returns Double
}
@Test
void basicParsesCorrectly() {
JsonParser parser = new BasicJsonParser();
Map<String, Object> result = parser.parseMap(JSON);
assertThat(result)
.containsEntry("key", "value")
.containsEntry("number", 42);
}
@Test
void handlesInvalidJson() {
JsonParser parser = JsonParserFactory.getJsonParser();
assertThatThrownBy(() -> parser.parseMap("{invalid}"))
.isInstanceOf(JsonParseException.class);
}
}Different parsers handle numeric types differently:
Jackson:
Integer or LongDoubleBooleanGson:
DoubleBooleanBasic:
Number (Integer, Long, or Double)BooleanAlways cast appropriately:
Map<String, Object> data = parser.parseMap(json);
// Safe casting
Object numberObj = data.get("count");
int count;
if (numberObj instanceof Number) {
count = ((Number) numberObj).intValue();
} else {
count = Integer.parseInt(numberObj.toString());
}Parser Selection Priority:
Production-ready JSON parsing with automatic parser selection and comprehensive error handling:
package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.BasicJsonParser;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Production-ready JSON parser with caching and fallback support.
* Automatically selects best available parser and caches parsed results.
*/
@Component
public class RobustJsonParser {
private final JsonParser primaryParser;
private final JsonParser fallbackParser;
private final Map<String, Map<String, Object>> mapCache;
private final Map<String, List<Object>> listCache;
private final int maxCacheSize;
public RobustJsonParser() {
this(1000);
}
public RobustJsonParser(int maxCacheSize) {
// Try to use Jackson, fallback to Basic
this.primaryParser = JsonParserFactory.getJsonParser();
this.fallbackParser = new BasicJsonParser();
this.mapCache = new ConcurrentHashMap<>();
this.listCache = new ConcurrentHashMap<>();
this.maxCacheSize = maxCacheSize;
}
/**
* Parse JSON string to Map with caching and fallback.
*
* @param json the JSON string
* @return parsed map
* @throws JsonParseException if parsing fails with all parsers
*/
public Map<String, Object> parseMap(String json) {
if (json == null || json.trim().isEmpty()) {
throw new IllegalArgumentException("JSON string cannot be null or empty");
}
// Check cache first
String cacheKey = generateCacheKey(json);
Map<String, Object> cached = mapCache.get(cacheKey);
if (cached != null) {
return cached;
}
try {
Map<String, Object> result = primaryParser.parseMap(json);
cacheResult(cacheKey, result);
return result;
} catch (JsonParseException e) {
// Try fallback parser
try {
Map<String, Object> result = fallbackParser.parseMap(json);
cacheResult(cacheKey, result);
return result;
} catch (JsonParseException fallbackException) {
throw new JsonParseException(
"Failed to parse JSON with all available parsers. " +
"Primary: " + e.getMessage() + ", " +
"Fallback: " + fallbackException.getMessage(),
e
);
}
}
}
/**
* Parse JSON string to List with caching and fallback.
*
* @param json the JSON string
* @return parsed list
* @throws JsonParseException if parsing fails with all parsers
*/
public List<Object> parseList(String json) {
if (json == null || json.trim().isEmpty()) {
throw new IllegalArgumentException("JSON string cannot be null or empty");
}
// Check cache first
String cacheKey = generateCacheKey(json);
List<Object> cached = listCache.get(cacheKey);
if (cached != null) {
return cached;
}
try {
List<Object> result = primaryParser.parseList(json);
cacheListResult(cacheKey, result);
return result;
} catch (JsonParseException e) {
// Try fallback parser
try {
List<Object> result = fallbackParser.parseList(json);
cacheListResult(cacheKey, result);
return result;
} catch (JsonParseException fallbackException) {
throw new JsonParseException(
"Failed to parse JSON array with all available parsers",
e
);
}
}
}
private void cacheResult(String key, Map<String, Object> result) {
if (mapCache.size() < maxCacheSize) {
mapCache.put(key, result);
}
}
private void cacheListResult(String key, List<Object> result) {
if (listCache.size() < maxCacheSize) {
listCache.put(key, result);
}
}
private String generateCacheKey(String json) {
return String.valueOf(json.hashCode());
}
public void clearCache() {
mapCache.clear();
listCache.clear();
}
}Extract and convert JSON values with type safety and validation:
package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
/**
* Type-safe JSON value extractor with comprehensive error handling.
* Handles different parser implementations and their type variations.
*/
public class JsonValueExtractor {
private final JsonParser parser;
public JsonValueExtractor() {
this.parser = JsonParserFactory.getJsonParser();
}
/**
* Extract string value from JSON.
*
* @param json the JSON string
* @param key the property key
* @return the string value or empty if not found
* @throws JsonParseException if JSON is invalid
*/
public Optional<String> getString(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
return obj.toString();
});
}
/**
* Extract integer value from JSON, handling different number types.
*
* @param json the JSON string
* @param key the property key
* @return the integer value or empty if not found
* @throws JsonParseException if JSON is invalid
* @throws NumberFormatException if value cannot be converted to integer
*/
public Optional<Integer> getInteger(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof Number) {
return ((Number) obj).intValue();
}
return Integer.parseInt(obj.toString());
});
}
/**
* Extract long value from JSON, handling different number types.
*
* @param json the JSON string
* @param key the property key
* @return the long value or empty if not found
* @throws JsonParseException if JSON is invalid
* @throws NumberFormatException if value cannot be converted to long
*/
public Optional<Long> getLong(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof Number) {
return ((Number) obj).longValue();
}
return Long.parseLong(obj.toString());
});
}
/**
* Extract double value from JSON, handling different number types.
*
* @param json the JSON string
* @param key the property key
* @return the double value or empty if not found
* @throws JsonParseException if JSON is invalid
* @throws NumberFormatException if value cannot be converted to double
*/
public Optional<Double> getDouble(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof Number) {
return ((Number) obj).doubleValue();
}
return Double.parseDouble(obj.toString());
});
}
/**
* Extract boolean value from JSON.
*
* @param json the JSON string
* @param key the property key
* @return the boolean value or empty if not found
* @throws JsonParseException if JSON is invalid
*/
public Optional<Boolean> getBoolean(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof Boolean) {
return (Boolean) obj;
}
return Boolean.parseBoolean(obj.toString());
});
}
/**
* Extract nested map from JSON.
*
* @param json the JSON string
* @param key the property key
* @return the nested map or empty if not found
* @throws JsonParseException if JSON is invalid
* @throws ClassCastException if value is not a map
*/
@SuppressWarnings("unchecked")
public Optional<Map<String, Object>> getMap(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof Map) {
return (Map<String, Object>) obj;
}
throw new ClassCastException("Value at key '" + key + "' is not a Map");
});
}
/**
* Extract list from JSON.
*
* @param json the JSON string
* @param key the property key
* @return the list or empty if not found
* @throws JsonParseException if JSON is invalid
* @throws ClassCastException if value is not a list
*/
@SuppressWarnings("unchecked")
public Optional<List<Object>> getList(String json, String key) {
return getValue(json, key, obj -> {
if (obj == null) return null;
if (obj instanceof List) {
return (List<Object>) obj;
}
throw new ClassCastException("Value at key '" + key + "' is not a List");
});
}
/**
* Generic value extraction with custom converter.
*
* @param json the JSON string
* @param key the property key
* @param converter function to convert raw object to desired type
* @return the converted value or empty if not found
* @throws JsonParseException if JSON is invalid
*/
private <T> Optional<T> getValue(String json, String key, Function<Object, T> converter) {
Map<String, Object> map = parser.parseMap(json);
Object value = map.get(key);
if (value == null) {
return Optional.empty();
}
try {
T converted = converter.apply(value);
return Optional.ofNullable(converted);
} catch (Exception e) {
throw new IllegalArgumentException(
"Failed to convert value at key '" + key + "': " + e.getMessage(),
e
);
}
}
/**
* Extract value from nested path (e.g., "user.address.city").
*
* @param json the JSON string
* @param path dot-separated path
* @return the value or empty if not found
* @throws JsonParseException if JSON is invalid
*/
@SuppressWarnings("unchecked")
public Optional<Object> getNestedValue(String json, String path) {
Map<String, Object> current = parser.parseMap(json);
String[] parts = path.split("\\.");
for (int i = 0; i < parts.length - 1; i++) {
Object next = current.get(parts[i]);
if (!(next instanceof Map)) {
return Optional.empty();
}
current = (Map<String, Object>) next;
}
return Optional.ofNullable(current.get(parts[parts.length - 1]));
}
}Process multiple JSON documents efficiently with streaming and error recovery:
package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Batch processor for handling multiple JSON documents with parallel processing.
* Includes error recovery and progress tracking.
*/
public class JsonBatchProcessor implements AutoCloseable {
private final JsonParser parser;
private final ExecutorService executor;
private final Consumer<ProcessingError> errorHandler;
private final int batchSize;
public JsonBatchProcessor(int threadCount, Consumer<ProcessingError> errorHandler) {
this(threadCount, errorHandler, 100);
}
public JsonBatchProcessor(int threadCount, Consumer<ProcessingError> errorHandler, int batchSize) {
this.parser = JsonParserFactory.getJsonParser();
this.executor = Executors.newFixedThreadPool(threadCount);
this.errorHandler = errorHandler;
this.batchSize = batchSize;
}
/**
* Process multiple JSON documents in parallel.
*
* @param jsonDocuments list of JSON strings to process
* @param processor function to process each parsed document
* @return list of processing results
*/
public <T> List<T> processBatch(
List<String> jsonDocuments,
JsonDocumentProcessor<T> processor) {
List<CompletableFuture<T>> futures = new ArrayList<>();
for (int i = 0; i < jsonDocuments.size(); i++) {
final int index = i;
final String json = jsonDocuments.get(i);
CompletableFuture<T> future = CompletableFuture.supplyAsync(() -> {
try {
Map<String, Object> parsed = parser.parseMap(json);
return processor.process(parsed, index);
} catch (JsonParseException e) {
errorHandler.accept(new ProcessingError(index, json, e));
return null;
} catch (Exception e) {
errorHandler.accept(new ProcessingError(index, json, e));
return null;
}
}, executor);
futures.add(future);
}
// Wait for all to complete and collect non-null results
return futures.stream()
.map(CompletableFuture::join)
.filter(result -> result != null)
.collect(Collectors.toList());
}
/**
* Process JSON documents in batches for better memory management.
*
* @param jsonDocuments list of JSON strings to process
* @param processor function to process each parsed document
* @return list of processing results
*/
public <T> List<T> processInBatches(
List<String> jsonDocuments,
JsonDocumentProcessor<T> processor) {
List<T> allResults = new ArrayList<>();
for (int i = 0; i < jsonDocuments.size(); i += batchSize) {
int end = Math.min(i + batchSize, jsonDocuments.size());
List<String> batch = jsonDocuments.subList(i, end);
List<T> batchResults = processBatch(batch, processor);
allResults.addAll(batchResults);
// Optional: Log progress
System.out.printf("Processed %d/%d documents%n", end, jsonDocuments.size());
}
return allResults;
}
/**
* Process JSON array string into individual documents.
*
* @param jsonArray JSON array string
* @param processor function to process each element
* @return list of processing results
*/
@SuppressWarnings("unchecked")
public <T> List<T> processJsonArray(
String jsonArray,
JsonDocumentProcessor<T> processor) {
List<Object> items = parser.parseList(jsonArray);
List<String> jsonStrings = new ArrayList<>();
// Convert each item back to JSON string for processing
for (Object item : items) {
if (item instanceof Map) {
// Simple JSON reconstruction (for production use proper serializer)
jsonStrings.add(reconstructJson((Map<String, Object>) item));
}
}
return processBatch(jsonStrings, processor);
}
private String reconstructJson(Map<String, Object> map) {
// Simplified JSON reconstruction - in production use Jackson/Gson
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(entry.getKey()).append("\":");
if (entry.getValue() instanceof String) {
sb.append("\"").append(entry.getValue()).append("\"");
} else {
sb.append(entry.getValue());
}
first = false;
}
sb.append("}");
return sb.toString();
}
@Override
public void close() {
executor.shutdown();
}
@FunctionalInterface
public interface JsonDocumentProcessor<T> {
T process(Map<String, Object> document, int index) throws Exception;
}
public static class ProcessingError {
public final int index;
public final String json;
public final Exception exception;
public ProcessingError(int index, String json, Exception exception) {
this.index = index;
this.json = json;
this.exception = exception;
}
@Override
public String toString() {
return String.format("Error at index %d: %s", index, exception.getMessage());
}
}
}Validate JSON structure and data types before processing:
package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Validates JSON documents against expected schemas.
* Provides detailed validation errors for debugging.
*/
public class JsonSchemaValidator {
private final JsonParser parser;
public JsonSchemaValidator() {
this.parser = JsonParserFactory.getJsonParser();
}
/**
* Validate JSON document against required fields.
*
* @param json the JSON string
* @param requiredFields set of required field names
* @return validation result
*/
public ValidationResult validateRequiredFields(String json, Set<String> requiredFields) {
List<String> errors = new ArrayList<>();
try {
Map<String, Object> map = parser.parseMap(json);
for (String field : requiredFields) {
if (!map.containsKey(field)) {
errors.add("Missing required field: " + field);
} else if (map.get(field) == null) {
errors.add("Required field has null value: " + field);
}
}
return new ValidationResult(errors.isEmpty(), errors);
} catch (JsonParseException e) {
errors.add("Invalid JSON: " + e.getMessage());
return new ValidationResult(false, errors);
}
}
/**
* Validate JSON field types.
*
* @param json the JSON string
* @param fieldTypes map of field name to expected type
* @return validation result
*/
public ValidationResult validateFieldTypes(String json, Map<String, Class<?>> fieldTypes) {
List<String> errors = new ArrayList<>();
try {
Map<String, Object> map = parser.parseMap(json);
for (Map.Entry<String, Class<?>> entry : fieldTypes.entrySet()) {
String field = entry.getKey();
Class<?> expectedType = entry.getValue();
if (!map.containsKey(field)) {
continue; // Field not present, skip type check
}
Object value = map.get(field);
if (value == null) {
continue; // Null values are allowed unless checked separately
}
if (!isCompatibleType(value, expectedType)) {
errors.add(String.format(
"Field '%s' has wrong type. Expected: %s, Actual: %s",
field,
expectedType.getSimpleName(),
value.getClass().getSimpleName()
));
}
}
return new ValidationResult(errors.isEmpty(), errors);
} catch (JsonParseException e) {
errors.add("Invalid JSON: " + e.getMessage());
return new ValidationResult(false, errors);
}
}
/**
* Validate JSON with custom validator function.
*
* @param json the JSON string
* @param customValidator custom validation function
* @return validation result
*/
public ValidationResult validateCustom(
String json,
java.util.function.Function<Map<String, Object>, List<String>> customValidator) {
try {
Map<String, Object> map = parser.parseMap(json);
List<String> errors = customValidator.apply(map);
return new ValidationResult(errors.isEmpty(), errors);
} catch (JsonParseException e) {
return new ValidationResult(false, List.of("Invalid JSON: " + e.getMessage()));
}
}
/**
* Comprehensive validation combining multiple checks.
*
* @param json the JSON string
* @param requiredFields required field names
* @param fieldTypes expected field types
* @return validation result
*/
public ValidationResult validateComprehensive(
String json,
Set<String> requiredFields,
Map<String, Class<?>> fieldTypes) {
List<String> allErrors = new ArrayList<>();
ValidationResult requiredResult = validateRequiredFields(json, requiredFields);
allErrors.addAll(requiredResult.errors);
if (requiredResult.valid) {
ValidationResult typeResult = validateFieldTypes(json, fieldTypes);
allErrors.addAll(typeResult.errors);
}
return new ValidationResult(allErrors.isEmpty(), allErrors);
}
private boolean isCompatibleType(Object value, Class<?> expectedType) {
// Handle Number types from different parsers
if (expectedType == Integer.class || expectedType == int.class) {
return value instanceof Number;
}
if (expectedType == Long.class || expectedType == long.class) {
return value instanceof Number;
}
if (expectedType == Double.class || expectedType == double.class) {
return value instanceof Number;
}
if (expectedType == Boolean.class || expectedType == boolean.class) {
return value instanceof Boolean;
}
if (expectedType == String.class) {
return value instanceof String;
}
if (expectedType == Map.class) {
return value instanceof Map;
}
if (expectedType == List.class) {
return value instanceof List;
}
return expectedType.isInstance(value);
}
public static class ValidationResult {
public final boolean valid;
public final List<String> errors;
public ValidationResult(boolean valid, List<String> errors) {
this.valid = valid;
this.errors = errors;
}
public void throwIfInvalid() {
if (!valid) {
throw new IllegalArgumentException(
"JSON validation failed: " + String.join(", ", errors)
);
}
}
@Override
public String toString() {
if (valid) {
return "Validation passed";
}
return "Validation failed: " + String.join("; ", errors);
}
}
}Load and parse configuration files with environment variable substitution:
package com.example.json;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.DefaultResourceLoader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Loads JSON configuration files with support for environment variable substitution.
* Handles classpath, file system, and URL resources.
*/
public class JsonConfigurationLoader {
private final JsonParser parser;
private final ResourceLoader resourceLoader;
private final Pattern envVarPattern;
public JsonConfigurationLoader() {
this(new DefaultResourceLoader());
}
public JsonConfigurationLoader(ResourceLoader resourceLoader) {
this.parser = JsonParserFactory.getJsonParser();
this.resourceLoader = resourceLoader;
this.envVarPattern = Pattern.compile("\\$\\{([^}]+)}");
}
/**
* Load configuration from classpath, file, or URL.
*
* @param location resource location (e.g., "classpath:config.json", "file:/etc/app/config.json")
* @return parsed configuration map
* @throws IOException if resource cannot be read
* @throws JsonParseException if JSON is invalid
*/
public Map<String, Object> loadConfiguration(String location) throws IOException {
Resource resource = resourceLoader.getResource(location);
if (!resource.exists()) {
throw new IOException("Configuration file not found: " + location);
}
String content = readResourceContent(resource);
String substituted = substituteEnvironmentVariables(content);
try {
return parser.parseMap(substituted);
} catch (JsonParseException e) {
throw new JsonParseException(
"Failed to parse configuration from " + location + ": " + e.getMessage(),
e
);
}
}
/**
* Load configuration with default fallback.
*
* @param location primary configuration location
* @param defaultLocation fallback configuration location
* @return parsed configuration map
* @throws IOException if both locations fail
*/
public Map<String, Object> loadConfigurationWithFallback(
String location,
String defaultLocation) throws IOException {
try {
return loadConfiguration(location);
} catch (IOException e) {
System.err.println("Primary config not found, using fallback: " + e.getMessage());
return loadConfiguration(defaultLocation);
}
}
/**
* Load configuration with profile support.
*
* @param baseName base configuration name without extension
* @param profile active profile (e.g., "dev", "prod")
* @return merged configuration map
* @throws IOException if configuration cannot be read
*/
public Map<String, Object> loadConfigurationWithProfile(
String baseName,
String profile) throws IOException {
// Load base configuration
Map<String, Object> config = loadConfiguration(baseName + ".json");
// Try to load profile-specific configuration
try {
String profileLocation = baseName + "-" + profile + ".json";
Map<String, Object> profileConfig = loadConfiguration(profileLocation);
// Merge profile config into base config (profile overrides base)
config.putAll(profileConfig);
} catch (IOException e) {
// Profile-specific config is optional
System.out.println("No profile-specific config found for: " + profile);
}
return config;
}
private String readResourceContent(Resource resource) throws IOException {
try (InputStream is = resource.getInputStream()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
/**
* Substitute environment variables in format ${VAR_NAME} or ${VAR_NAME:default}.
*
* @param content content with environment variable placeholders
* @return content with substituted values
*/
private String substituteEnvironmentVariables(String content) {
Matcher matcher = envVarPattern.matcher(content);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String varExpression = matcher.group(1);
String replacement = resolveEnvironmentVariable(varExpression);
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
private String resolveEnvironmentVariable(String expression) {
// Support format: VAR_NAME or VAR_NAME:defaultValue
String[] parts = expression.split(":", 2);
String varName = parts[0].trim();
String defaultValue = parts.length > 1 ? parts[1].trim() : "";
String value = System.getenv(varName);
if (value == null) {
value = System.getProperty(varName);
}
return (value != null) ? value : defaultValue;
}
/**
* Validate configuration against schema.
*
* @param location configuration location
* @param validator schema validator
* @return validated configuration map
* @throws IOException if resource cannot be read
* @throws IllegalArgumentException if validation fails
*/
public Map<String, Object> loadAndValidate(
String location,
JsonSchemaValidator validator,
java.util.Set<String> requiredFields) throws IOException {
Map<String, Object> config = loadConfiguration(location);
// Convert map back to JSON string for validation
// In production, use proper JSON serializer
String jsonString = reconstructJsonFromMap(config);
JsonSchemaValidator.ValidationResult result =
validator.validateRequiredFields(jsonString, requiredFields);
result.throwIfInvalid();
return config;
}
private String reconstructJsonFromMap(Map<String, Object> map) {
// Simplified reconstruction - use Jackson/Gson in production
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(entry.getKey()).append("\":");
appendValue(sb, entry.getValue());
first = false;
}
sb.append("}");
return sb.toString();
}
@SuppressWarnings("unchecked")
private void appendValue(StringBuilder sb, Object value) {
if (value == null) {
sb.append("null");
} else if (value instanceof String) {
sb.append("\"").append(value).append("\"");
} else if (value instanceof Map) {
sb.append(reconstructJsonFromMap((Map<String, Object>) value));
} else if (value instanceof List) {
sb.append("[");
List<?> list = (List<?>) value;
for (int i = 0; i < list.size(); i++) {
if (i > 0) sb.append(",");
appendValue(sb, list.get(i));
}
sb.append("]");
} else {
sb.append(value);
}
}
}Problem: Different parsers return different number types, causing ClassCastException
Error:
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.IntegerSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;
public class SafeNumberExtraction {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public int extractIntSafely(String json, String key) {
Map<String, Object> map = parser.parseMap(json);
Object value = map.get(key);
// Wrong - causes ClassCastException with Gson
// Integer num = (Integer) value;
// Correct - handles all parser types
if (value instanceof Number) {
return ((Number) value).intValue();
}
throw new IllegalArgumentException("Value at key '" + key + "' is not a number");
}
}Rationale: Jackson returns Integer/Long/Double based on value, Gson always returns Double for decimals, and BasicJsonParser returns Number. Always cast to Number first, then convert to specific type.
Problem: Attempting to cast null values from parsed JSON
Error:
java.lang.NullPointerException: Cannot invoke method on null objectSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;
public class SafeValueAccess {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public String getValueSafely(String json, String key) {
Map<String, Object> map = parser.parseMap(json);
// Wrong - throws NPE if key doesn't exist or value is null
// return map.get(key).toString();
// Correct - check for existence and null
if (!map.containsKey(key)) {
return "KEY_NOT_FOUND";
}
Object value = map.get(key);
return (value != null) ? value.toString() : "NULL_VALUE";
}
}Rationale: JSON null values are represented as Java null in the parsed map. Always check for null before accessing or converting values to prevent NullPointerException.
Problem: JsonParseException is unchecked, leading to improper resource cleanup
Error: Resource leaks when parsing fails in the middle of resource handling
Solution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
public class SafeFileJsonParsing {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Map<String, Object> parseJsonFile(String filePath) throws IOException {
// Wrong - resource leak if JsonParseException occurs
// BufferedReader reader = new BufferedReader(new FileReader(filePath));
// String json = reader.readLine();
// return parser.parseMap(json);
// Correct - use try-with-resources
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
StringBuilder json = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
json.append(line);
}
return parser.parseMap(json.toString());
} catch (JsonParseException e) {
throw new IOException("Failed to parse JSON from file: " + filePath, e);
}
}
}Rationale: JsonParseException is a RuntimeException and can be thrown at any time. Use try-with-resources to ensure proper cleanup even when parsing fails.
Problem: Hardcoding parser selection instead of using factory
Error:
java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapperSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JacksonJsonParser;
public class ParserSelection {
// Wrong - fails if Jackson not on classpath
// private final JsonParser parser = new JacksonJsonParser();
// Correct - automatically selects best available parser
private final JsonParser parser = JsonParserFactory.getJsonParser();
// Also correct - explicitly specify with fallback handling
private JsonParser createParserWithFallback() {
try {
return new JacksonJsonParser();
} catch (NoClassDefFoundError e) {
System.out.println("Jackson not available, using factory selection");
return JsonParserFactory.getJsonParser();
}
}
}Rationale: JsonParserFactory automatically detects available parsers and selects the best one. Hardcoding parser types causes ClassNotFoundException when dependencies are missing.
Problem: Parsing invalid or malformed JSON without validation
Error:
org.springframework.boot.json.JsonParseException: Cannot parse JSONSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.Map;
public class ValidatedJsonParsing {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Map<String, Object> parseWithValidation(String json) {
// Validate input
if (json == null || json.trim().isEmpty()) {
throw new IllegalArgumentException("JSON string cannot be null or empty");
}
// Check basic JSON structure
String trimmed = json.trim();
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
throw new IllegalArgumentException("JSON must be an object (start with { and end with })");
}
// Parse with error handling
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
// Provide context in error message
String preview = json.length() > 100 ? json.substring(0, 100) + "..." : json;
throw new JsonParseException(
"Failed to parse JSON. Preview: " + preview + ". Error: " + e.getMessage(),
e
);
}
}
}Rationale: Pre-validation catches common issues early and provides better error messages. Always validate input before attempting expensive parsing operations.
Problem: Directly casting nested objects without type checking
Error:
java.lang.ClassCastException: java.lang.String cannot be cast to java.util.MapSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.Map;
public class SafeNestedAccess {
private final JsonParser parser = JsonParserFactory.getJsonParser();
@SuppressWarnings("unchecked")
public String getNestedValueSafely(String json, String parentKey, String childKey) {
Map<String, Object> map = parser.parseMap(json);
// Wrong - crashes if parent is not a Map
// Map<String, Object> nested = (Map<String, Object>) map.get(parentKey);
// return (String) nested.get(childKey);
// Correct - verify type before casting
Object parentObj = map.get(parentKey);
if (!(parentObj instanceof Map)) {
throw new IllegalArgumentException(
"Expected Map at key '" + parentKey + "' but found: " +
(parentObj != null ? parentObj.getClass().getSimpleName() : "null")
);
}
Map<String, Object> nested = (Map<String, Object>) parentObj;
Object value = nested.get(childKey);
return (value != null) ? value.toString() : null;
}
}Rationale: JSON structure can vary, and nested objects might be missing or of unexpected types. Always verify types before casting to prevent ClassCastException.
Problem: Parsing very large JSON documents into memory all at once
Error:
java.lang.OutOfMemoryError: Java heap spaceSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StreamingJsonParsing {
private final JsonParser parser = JsonParserFactory.getJsonParser();
// Wrong - loads entire file into memory
public void processLargeFileWrong(String filePath) throws Exception {
String entireJson = new String(java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get(filePath)
));
List<Object> items = parser.parseList(entireJson); // OOM if file is huge
}
// Correct - process line by line if JSON Lines format
public void processJsonLines(String filePath,
java.util.function.Consumer<Map<String, Object>> processor)
throws Exception {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
try {
if (!line.trim().isEmpty()) {
Map<String, Object> item = parser.parseMap(line);
processor.accept(item);
}
} catch (Exception e) {
System.err.println("Error parsing line " + lineNumber + ": " + e.getMessage());
}
}
}
}
}Rationale: Spring Boot's JsonParser loads entire JSON into memory. For large files, use streaming parsers like Jackson's JsonParser or process JSON Lines format. Consider limiting document size or implementing pagination.
Problem: Assuming arrays and objects always contain elements
Error:
java.util.NoSuchElementException: No value present
java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0Solution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.util.List;
import java.util.Map;
public class SafeCollectionAccess {
private final JsonParser parser = JsonParserFactory.getJsonParser();
public Object getFirstArrayElement(String jsonArray) {
List<Object> list = parser.parseList(jsonArray);
// Wrong - crashes on empty array
// return list.get(0);
// Correct - check size first
if (list.isEmpty()) {
return null; // or throw meaningful exception
}
return list.get(0);
}
public boolean hasAnyKeys(String json) {
Map<String, Object> map = parser.parseMap(json);
// Correct - handle empty objects
if (map.isEmpty()) {
System.out.println("JSON object is empty");
return false;
}
return true;
}
}Rationale: Valid JSON can contain empty arrays [] and empty objects {}. Always check collection size before accessing elements to prevent IndexOutOfBoundsException.
Problem: Catching JsonParseException but continuing with null or empty results
Error: Silent failures leading to incorrect application behavior
Solution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
import java.util.Map;
import java.util.Collections;
public class ProperErrorRecovery {
private final JsonParser parser = JsonParserFactory.getJsonParser();
// Wrong - swallows errors and returns empty map
public Map<String, Object> parseJsonWrong(String json) {
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
e.printStackTrace();
return Collections.emptyMap(); // Hides the problem!
}
}
// Correct - fail fast with clear error
public Map<String, Object> parseJsonCorrect(String json) {
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
throw new IllegalArgumentException(
"Failed to parse JSON configuration. " +
"Please verify the JSON syntax. Error: " + e.getMessage(),
e
);
}
}
// Also correct - provide default only if appropriate
public Map<String, Object> parseJsonWithDefault(String json,
Map<String, Object> defaultConfig) {
try {
return parser.parseMap(json);
} catch (JsonParseException e) {
System.err.println("JSON parsing failed, using default config: " + e.getMessage());
return defaultConfig;
}
}
}Rationale: Silently recovering from parse errors by returning empty collections hides problems and causes hard-to-debug issues. Either fail fast with clear errors or provide sensible defaults with logging.
Problem: Not handling Unicode characters and escape sequences properly
Error:
org.springframework.boot.json.JsonParseException: Invalid escape sequence
Garbled text for non-ASCII charactersSolution:
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
public class UnicodeJsonParsing {
private final JsonParser parser = JsonParserFactory.getJsonParser();
// Wrong - uses platform default encoding
public void readJsonFileWrong(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath)) {
// May use wrong encoding (e.g., Windows-1252 on Windows)
Reader reader = new InputStreamReader(fis);
// ... rest of code
}
}
// Correct - explicitly use UTF-8
public String readJsonFileCorrect(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
StringBuilder json = new StringBuilder();
char[] buffer = new char[8192];
int charsRead;
while ((charsRead = isr.read(buffer)) != -1) {
json.append(buffer, 0, charsRead);
}
return json.toString();
}
}
// Handle JSON with escaped Unicode
public void demonstrateUnicodeHandling() {
// JSON with Unicode escape sequences
String json = "{\"message\":\"Hello \\u4E16\\u754C\"}"; // "Hello 世界"
Map<String, Object> map = parser.parseMap(json);
// Parser automatically handles Unicode escapes
String message = (String) map.get("message");
System.out.println(message); // Prints: Hello 世界
}
}Rationale: JSON is UTF-8 by default. Always use StandardCharsets.UTF_8 when reading JSON files to prevent encoding issues. The parsers handle Unicode escape sequences automatically.
All parser implementations are thread-safe and can be safely shared across threads.
JsonParserFactory select best parser// Core interfaces
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.boot.json.JsonParseException;
// Specific implementations
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.json.GsonJsonParser;
import org.springframework.boot.json.BasicJsonParser;
// Java types
import java.util.Map;
import java.util.List;