Modern JSON Processing: A Complete Guide to JSON-B (JSON Binding) in Java

In the world of modern Java applications, JSON has become the de facto standard for data exchange. While libraries like Jackson and Gson have long been popular for JSON processing, Java EE 8 (and Jakarta EE) introduced a standardized API for JSON binding: JSON-B (JSON Binding). This specification provides a clean, annotation-based approach for converting Java objects to and from JSON, reducing boilerplate code and increasing portability across implementations.

This article explores JSON-B, the Java standard for JSON binding, covering its core concepts, annotations, and practical usage patterns.


What is JSON-B?

JSON-B (JSR 367) is a standard Java API for converting Java objects to and from JSON documents. It's part of the Jakarta EE (formerly Java EE) ecosystem and provides a consistent, annotation-driven approach to JSON processing.

Key Benefits:

  • Standardization: Portable across different implementations
  • Simplicity: Minimal configuration required for common cases
  • Flexibility: Extensive customization through annotations
  • Type Safety: Works with strongly-typed Java objects
  • Integration: Seamlessly integrates with JAX-RS and other Jakarta EE technologies

JSON-B Implementations

Two main implementations of JSON-B are available:

  1. Eclipse Yasson (Reference Implementation)
  2. Apache Johnzon

Maven Dependencies:

For Yasson:

<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.1</version>
</dependency>

For Johnzon:

<dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-jsonb</artifactId>
<version>1.2.20</version>
</dependency>

Basic JSON-B Usage

The entry point to JSON-B is the Jsonb interface, obtained through JsonbBuilder.

Basic Serialization and Deserialization:

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
public class JsonbBasicExample {
public static void main(String[] args) throws Exception {
// Create Jsonb instance with default configuration
try (Jsonb jsonb = JsonbBuilder.create()) {
// Sample object
User user = new User("john_doe", "[email protected]", 30);
// Serialize to JSON
String json = jsonb.toJson(user);
System.out.println("Serialized JSON: " + json);
// Output: {"age":30,"email":"[email protected]","username":"john_doe"}
// Deserialize from JSON
User deserializedUser = jsonb.fromJson(json, User.class);
System.out.println("Deserialized user: " + deserializedUser.getUsername());
}
}
}
class User {
private String username;
private String email;
private int age;
// Default constructor required by JSON-B
public User() {}
public User(String username, String email, int age) {
this.username = username;
this.email = email;
this.age = age;
}
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}

JSON-B Annotations for Customization

JSON-B provides annotations to customize the binding process without modifying your Java objects.

1. @JsonbProperty - Custom Property Names

import jakarta.json.bind.annotation.JsonbProperty;
class Product {
private Long id;
@JsonbProperty("productName")
private String name;
@JsonbProperty("unitCost")
private double price;
@JsonbProperty(nillable = true)
private String description;
// Constructors, getters, and setters
public Product() {}
public Product(Long id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}
// Usage
Product product = new Product(1L, "Laptop", 999.99);
// Serializes to: {"id":1,"productName":"Laptop","unitCost":999.99}

2. @JsonbTransient - Ignoring Properties

import jakarta.json.bind.annotation.JsonbTransient;
class SecureUser {
private String username;
private String email;
@JsonbTransient
private String password;
@JsonbTransient
private String socialSecurityNumber;
// Constructors, getters, and setters
public SecureUser() {}
public SecureUser(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// Getters and setters...
}
// Password and SSN won't appear in JSON output

3. @JsonbDateFormat - Custom Date Formatting

import jakarta.json.bind.annotation.JsonbDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
class Event {
private String name;
@JsonbDateFormat("yyyy-MM-dd")
private LocalDate eventDate;
@JsonbDateFormat("yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
@JsonbDateFormat("MMM dd, yyyy")
private Date createdDate;
// Constructors, getters, and setters
public Event() {}
public Event(String name, LocalDate eventDate, LocalDateTime startTime) {
this.name = name;
this.eventDate = eventDate;
this.startTime = startTime;
this.createdDate = new Date();
}
// Getters and setters...
}

4. @JsonbNumberFormat - Number Formatting

import jakarta.json.bind.annotation.JsonbNumberFormat;
class FinancialRecord {
private String accountNumber;
@JsonbNumberFormat("#0.00")
private double balance;
@JsonbNumberFormat("##0%")
private double interestRate;
// Constructors, getters, and setters...
}

5. @JsonbNillable and @JsonbPropertyOrder

import jakarta.json.bind.annotation.JsonbNillable;
import jakarta.json.bind.annotation.JsonbPropertyOrder;
@JsonbNillable
@JsonbPropertyOrder({"name", "id", "price", "category"})
public class OrderedProduct {
private Long id;
private String name;
private Double price;
private String category;
// Constructors, getters, and setters...
}

Advanced JSON-B Features

1. Custom Adapters for Complex Mapping

import jakarta.json.bind.adapter.JsonbAdapter;
// Custom type that needs special handling
class Price {
private final double amount;
private final String currency;
public Price(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// Getters...
public double getAmount() { return amount; }
public String getCurrency() { return currency; }
@Override
public String toString() {
return amount + " " + currency;
}
}
// Adapter for Price class
class PriceAdapter implements JsonbAdapter<Price, String> {
@Override
public String adaptToJson(Price price) {
return price.getAmount() + " " + price.getCurrency();
}
@Override
public Price adaptFromJson(String adapted) {
String[] parts = adapted.split(" ");
return new Price(Double.parseDouble(parts[0]), parts[1]);
}
}
// Using the adapter
class ProductWithPrice {
private String name;
@jakarta.json.bind.annotation.JsonbTypeAdapter(PriceAdapter.class)
private Price price;
// Constructors, getters, and setters...
}

2. Custom Serializers and Deserializers

import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.JsonbDeserializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.bind.serializer.DeserializationContext;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;
class SecureDataSerializer implements JsonbSerializer<String> {
@Override
public void serialize(String obj, JsonGenerator generator, SerializationContext ctx) {
// Mask sensitive data
if (obj != null && obj.length() > 4) {
String masked = "***" + obj.substring(obj.length() - 4);
generator.write(masked);
} else {
generator.write("***");
}
}
}
class UserWithSensitiveData {
private String username;
@jakarta.json.bind.annotation.JsonbTypeSerializer(SecureDataSerializer.class)
private String creditCardNumber;
// Constructors, getters, and setters...
}

Configuration and Customization

Global Configuration with JsonbConfig:

import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.config.PropertyNamingStrategy;
import jakarta.json.bind.config.PropertyOrderStrategy;
JsonbConfig config = new JsonbConfig()
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES)
.withNullValues(true)
.withFormatting(true)  // Pretty-print JSON
.withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL)
.withEncoding("UTF-8");
try (Jsonb jsonb = JsonbBuilder.create(config)) {
User user = new User("john_doe", null, 30);
String json = jsonb.toJson(user);
System.out.println(json);
// Output with pretty printing and null values included
}

Custom Property Naming Strategies:

JsonbConfig config = new JsonbConfig()
.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES)
// or CASE_INSENSITIVE, IDENTITY, UPPER_CAMEL_CASE, etc.

Working with Collections and Complex Objects

Collections and Maps:

import java.util.List;
import java.util.Map;
import java.util.HashMap;
class Department {
private String name;
private List<Employee> employees;
private Map<String, String> metadata;
public Department() {
this.metadata = new HashMap<>();
}
public Department(String name, List<Employee> employees) {
this();
this.name = name;
this.employees = employees;
}
// Getters and setters...
}
class Employee {
private String name;
private String position;
// Constructors, getters, and setters...
}
// Usage
List<Employee> employees = List.of(
new Employee("Alice", "Developer"),
new Employee("Bob", "Manager")
);
Department dept = new Department("Engineering", employees);
dept.getMetadata().put("location", "New York");
dept.getMetadata().put("budget", "1M");
try (Jsonb jsonb = JsonbBuilder.create()) {
String json = jsonb.toJson(dept);
System.out.println(json);
}

Inheritance and Polymorphism:

import jakarta.json.bind.annotation.JsonbSubtype;
import jakarta.json.bind.annotation.JsonbTypeInfo;
@JsonbTypeInfo({
@JsonbSubtype(alias = "circle", type = Circle.class),
@JsonbSubtype(alias = "rectangle", type = Rectangle.class)
})
abstract class Shape {
private String color;
// Getters and setters...
}
class Circle extends Shape {
private double radius;
// Constructors, getters, and setters...
}
class Rectangle extends Shape {
private double width;
private double height;
// Constructors, getters, and setters...
}

Integration with JAX-RS

JSON-B works seamlessly with JAX-RS for building RESTful web services:

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@GET
public List<User> getUsers() {
// JSON-B automatically serializes to JSON
return userService.getAllUsers();
}
@GET
@Path("/{id}")
public User getUser(@PathParam("id") Long id) {
return userService.getUserById(id);
}
@POST
public Response createUser(User user) {
// JSON-B automatically deserializes from JSON
User created = userService.createUser(user);
return Response.status(Response.Status.CREATED).entity(created).build();
}
}

Error Handling

try (Jsonb jsonb = JsonbBuilder.create()) {
// Deserialization with error handling
try {
User user = jsonb.fromJson(invalidJson, User.class);
} catch (Exception e) {
System.err.println("Failed to deserialize JSON: " + e.getMessage());
}
// Serialization with error handling
try {
String json = jsonb.toJson(circularReferenceObject);
} catch (Exception e) {
System.err.println("Failed to serialize object: " + e.getMessage());
}
}

Best Practices

  1. Always use default constructors for classes used with JSON-B
  2. Use annotations sparingly - rely on conventions when possible
  3. Implement custom adapters for complex transformation logic
  4. Configure naming strategies globally rather than per-class
  5. Handle null values explicitly using @JsonbNillable
  6. Use pretty printing in development but not production
  7. Validate JSON before deserialization in production systems

Conclusion

JSON-B provides a standardized, powerful approach to JSON processing in Java applications. Key advantages include:

  • Standardization: Portable across different implementations
  • Simplicity: Convention-over-configuration approach
  • Flexibility: Extensive customization through annotations and adapters
  • Integration: Seamless work with Jakarta EE technologies
  • Type Safety: Compile-time safety with Java objects

Whether you're building RESTful web services, configuring applications, or exchanging data between systems, JSON-B offers a robust and standardized solution for all your JSON binding needs in modern Java applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper