What is JSON Processing in Jakarta EE?
Think of JSON Processing like mail handling:
- JSON-P (Processing): Like opening and reading letters manually (low-level)
- JSON-B (Binding): Like having a secretary who automatically files everything (high-level)
Jakarta JSON Processing (JSON-P) provides a low-level API for parsing, generating, transforming, and querying JSON data.
Jakarta JSON Binding (JSON-B) provides automatic conversion between Java objects and JSON.
| Feature | JSON-P (Processing) | JSON-B (Binding) |
|---|---|---|
| Level | Low-level API | High-level API |
| Purpose | Parse/generate JSON | Object <-> JSON conversion |
| Control | Full manual control | Automatic with annotations |
| Use Case | Dynamic JSON, streaming | POJOs, DTOs |
| Package | jakarta.json | jakarta.json.bind |
JSON-P: Processing API
Creating JSON Objects
import jakarta.json.*;
// Create JSON object using builder
JsonObject person = Json.createObjectBuilder()
.add("name", "John Doe")
.add("age", 30)
.add("email", "john@example.com")
.add("active", true)
.add("address", Json.createObjectBuilder()
.add("street", "123 Main St")
.add("city", "New York")
.add("zip", "10001"))
.add("phones", Json.createArrayBuilder()
.add("555-1234")
.add("555-5678"))
.build();
// Convert to string
String jsonString = person.toString();
// {"name":"John Doe","age":30,"email":"john@example.com",...}
Parsing JSON
import jakarta.json.*;
import java.io.StringReader;
String json = """
{
"name": "Jane Smith",
"age": 25,
"skills": ["Java", "Python", "SQL"]
}
""";
// Parse JSON string
try (JsonReader reader = Json.createReader(new StringReader(json))) {
JsonObject obj = reader.readObject();
// Access values
String name = obj.getString("name"); // "Jane Smith"
int age = obj.getInt("age"); // 25
JsonArray skills = obj.getJsonArray("skills"); // ["Java", "Python", "SQL"]
// Iterate array
for (JsonValue skill : skills) {
System.out.println(skill);
}
}
Streaming API (Large Files)
// Streaming parser - memory efficient for large files
try (JsonParser parser = Json.createParser(new FileReader("large.json"))) {
while (parser.hasNext()) {
JsonParser.Event event = parser.next();
switch (event) {
case KEY_NAME -> System.out.println("Key: " + parser.getString());
case VALUE_STRING -> System.out.println("String: " + parser.getString());
case VALUE_NUMBER -> System.out.println("Number: " + parser.getBigDecimal());
case VALUE_TRUE -> System.out.println("Boolean: true");
case VALUE_FALSE -> System.out.println("Boolean: false");
case VALUE_NULL -> System.out.println("Null");
case START_OBJECT -> System.out.println("{");
case END_OBJECT -> System.out.println("}");
case START_ARRAY -> System.out.println("[");
case END_ARRAY -> System.out.println("]");
}
}
}
// Streaming generator - write large files efficiently
try (JsonGenerator gen = Json.createGenerator(new FileWriter("output.json"))) {
gen.writeStartArray();
for (int i = 0; i < 1000000; i++) {
gen.writeStartObject()
.write("id", i)
.write("name", "Item " + i)
.writeEnd();
}
gen.writeEnd();
}
JSON Pointer (Query JSON)
String json = """
{
"store": {
"books": [
{"title": "Java Guide", "price": 29.99},
{"title": "Spring Boot", "price": 39.99}
]
}
}
""";
JsonObject obj = Json.createReader(new StringReader(json)).readObject();
// Use JSON Pointer to navigate
JsonPointer pointer = Json.createPointer("/store/books/0/title");
JsonValue title = pointer.getValue(obj);
System.out.println(title); // "Java Guide"
// Get second book's price
JsonPointer pricePointer = Json.createPointer("/store/books/1/price");
JsonNumber price = (JsonNumber) pricePointer.getValue(obj);
System.out.println(price.doubleValue()); // 39.99
JSON Patch (Modify JSON)
JsonObject original = Json.createObjectBuilder()
.add("name", "John")
.add("age", 25)
.build();
// Create patch operations
JsonPatch patch = Json.createPatchBuilder()
.replace("/name", "John Doe") // Update name
.add("/email", "john@mail.com") // Add email
.remove("/age") // Remove age
.build();
// Apply patch
JsonObject patched = patch.apply(original);
// {"name":"John Doe","email":"john@mail.com"}
JSON-B: Binding API
Basic Serialization/Deserialization
import jakarta.json.bind.*;
// Your POJO
public class Person {
private String name;
private int age;
private String email;
// Constructors, getters, setters
public Person() {}
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// getters and setters...
}
// Create Jsonb instance
Jsonb jsonb = JsonbBuilder.create();
// Object to JSON (Serialization)
Person person = new Person("John", 30, "john@example.com");
String json = jsonb.toJson(person);
// {"age":30,"email":"john@example.com","name":"John"}
// JSON to Object (Deserialization)
String jsonInput = """
{"name":"Jane","age":25,"email":"jane@example.com"}
""";
Person parsed = jsonb.fromJson(jsonInput, Person.class);
System.out.println(parsed.getName()); // Jane
Collections and Generics
Jsonb jsonb = JsonbBuilder.create();
// List of objects
List<Person> people = List.of(
new Person("John", 30, "john@mail.com"),
new Person("Jane", 25, "jane@mail.com")
);
// Serialize list
String json = jsonb.toJson(people);
// [{"age":30,"email":"john@mail.com","name":"John"},...]
// Deserialize list (need Type for generics)
String jsonList = "[{\"name\":\"John\",\"age\":30},{\"name\":\"Jane\",\"age\":25}]";
List<Person> parsed = jsonb.fromJson(
jsonList,
new ArrayList<Person>(){}.getClass().getGenericSuperclass()
);
// Map serialization
Map<String, Integer> scores = Map.of("math", 95, "science", 88);
String mapJson = jsonb.toJson(scores);
// {"math":95,"science":88}
Customizing with Annotations
import jakarta.json.bind.annotation.*;
public class Employee {
@JsonbProperty("employee_id") // Custom JSON key name
private Long id;
@JsonbProperty("full_name")
private String name;
@JsonbTransient // Exclude from JSON
private String password;
@JsonbDateFormat("yyyy-MM-dd") // Custom date format
private LocalDate hireDate;
@JsonbNumberFormat("#,##0.00") // Custom number format
private BigDecimal salary;
@JsonbNillable // Include null values in JSON
private String middleName;
// getters/setters
}
// Result:
// {
// "employee_id": 123,
// "full_name": "John Doe",
// "hireDate": "2024-01-15",
// "salary": "75,000.00",
// "middleName": null
// }
Property Order and Naming Strategy
@JsonbPropertyOrder({"id", "name", "email", "phone"}) // Define order
public class Contact {
private Long id;
private String name;
private String email;
private String phone;
}
// Configure naming strategy
JsonbConfig config = new JsonbConfig()
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);
Jsonb jsonb = JsonbBuilder.create(config);
public class UserProfile {
private String firstName; // becomes "first_name"
private String lastName; // becomes "last_name"
private String emailAddress; // becomes "email_address"
}
Custom Serializer/Deserializer
// Custom serializer
public class MoneySerializer implements JsonbSerializer<Money> {
@Override
public void serialize(Money money, JsonGenerator generator,
SerializationContext ctx) {
generator.writeStartObject();
generator.write("amount", money.getAmount());
generator.write("currency", money.getCurrency().getCurrencyCode());
generator.writeEnd();
}
}
// Custom deserializer
public class MoneyDeserializer implements JsonbDeserializer<Money> {
@Override
public Money deserialize(JsonParser parser,
DeserializationContext ctx, Type rtType) {
JsonObject obj = parser.getObject();
BigDecimal amount = obj.getJsonNumber("amount").bigDecimalValue();
String currencyCode = obj.getString("currency");
return new Money(amount, Currency.getInstance(currencyCode));
}
}
// Use custom serializers
public class Order {
private Long id;
@JsonbTypeSerializer(MoneySerializer.class)
@JsonbTypeDeserializer(MoneyDeserializer.class)
private Money totalAmount;
}
Jsonb Configuration
JsonbConfig config = new JsonbConfig()
// Pretty print JSON
.withFormatting(true)
// Include null values
.withNullValues(true)
// Property naming strategy
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES)
// Property visibility (include private fields)
.withPropertyVisibilityStrategy(new PropertyVisibilityStrategy() {
@Override
public boolean isVisible(Field field) { return true; }
@Override
public boolean isVisible(Method method) { return true; }
})
// Date format
.withDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
// Property ordering
.withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL);
Jsonb jsonb = JsonbBuilder.create(config);
Integration with JAX-RS
@Path("/users")
public class UserResource {
// JSON-B automatically converts User to/from JSON!
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<User> getAll() {
return userService.findAll(); // Auto-converted to JSON
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public User create(User user) { // Auto-parsed from JSON
return userService.save(user);
}
}
// Custom Jsonb provider for JAX-RS
@Provider
public class JsonbContextResolver implements ContextResolver<Jsonb> {
private final Jsonb jsonb;
public JsonbContextResolver() {
JsonbConfig config = new JsonbConfig()
.withFormatting(true)
.withNullValues(false);
this.jsonb = JsonbBuilder.create(config);
}
@Override
public Jsonb getContext(Class<?> type) {
return jsonb;
}
}
Common Patterns
DTO with Validation
public class CreateUserRequest {
@NotNull
@Size(min = 2, max = 50)
@JsonbProperty("username")
private String name;
@NotNull
@Email
private String email;
@JsonbTransient
private String internalCode;
@JsonbCreator // Use this constructor for deserialization
public CreateUserRequest(
@JsonbProperty("username") String name,
@JsonbProperty("email") String email) {
this.name = name;
this.email = email;
}
}
Polymorphic Types
// Base type
public abstract class Notification {
private String type;
private String message;
}
// Subtypes
public class EmailNotification extends Notification {
private String recipient;
private String subject;
}
public class SmsNotification extends Notification {
private String phoneNumber;
}
// Custom deserializer for polymorphic types
public class NotificationDeserializer implements JsonbDeserializer<Notification> {
@Override
public Notification deserialize(JsonParser parser,
DeserializationContext ctx, Type rtType) {
JsonObject obj = parser.getObject();
String type = obj.getString("type");
return switch (type) {
case "email" -> ctx.deserialize(EmailNotification.class, parser);
case "sms" -> ctx.deserialize(SmsNotification.class, parser);
default -> throw new JsonbException("Unknown type: " + type);
};
}
}
Best Practices
DO:
- Use JSON-B for POJOs - Simpler and cleaner code
- Use JSON-P for dynamic JSON - When structure is unknown
- Use streaming for large files - Better memory efficiency
- Use DTOs - Don't expose entities directly
- Configure Jsonb once - Reuse the instance
- Use @JsonbTransient - Hide sensitive fields
- Handle nulls properly - Configure null handling strategy
DON'T:
- Don't create Jsonb per request - It's expensive
- Don't expose passwords/secrets - Use @JsonbTransient
- Don't ignore date formatting - Use @JsonbDateFormat
- Don't mix JSON-P and JSON-B unnecessarily - Pick the right tool
- Don't forget to close readers/parsers - Use try-with-resources
Summary
- JSON-P: Low-level API for parsing, generating, and querying JSON
- JSON-B: High-level binding between Java objects and JSON
- JsonObject/JsonArray: JSON-P immutable data structures
- Jsonb: Main JSON-B interface for toJson/fromJson
- @JsonbProperty: Customize JSON field names
- @JsonbTransient: Exclude fields from JSON
- @JsonbDateFormat: Custom date formatting
- JsonbConfig: Configure serialization behavior
- Streaming API: Memory-efficient for large files
- JSON Pointer/Patch: Query and modify JSON documents