Chapter 32: Processing Raw JSON Strings in Quarkus with Jackson and Event Bus Efficiency

Introduction

Efficiently handling JSON in Quarkus with Jackson and Vert.x Event Bus is crucial for memory optimization and high-performance microservices. Many developers face challenges such as unnecessary JSON copies, memory bloat, and improper garbage collection behavior when processing raw JSON strings in intra-JVM communication.

πŸ’‘ Key Challenge: JSON data from REST APIs often arrives as raw strings. If not handled properly, multiple copies of the same JSON object are created when passed between Verticles over the Event Bus. This can degrade performance and increase memory usage.

This blog explores the best practices for handling raw JSON strings in Quarkus while ensuring efficient memory management and transformation strategies using Jackson, Mutiny, and functional interfaces.


1️⃣ Why Pass JSON as Strings Over the Event Bus?

In intra-JVM communication, passing JSON as immutable Strings offers several advantages:

βœ… Java Strings are immutable, meaning they can be safely shared across Verticles without duplication. βœ… Passing Strings over the Event Bus avoids object duplication, reducing unnecessary memory overhead. βœ… No additional serialization/deserialization between Verticles improves performance. βœ… Garbage Collection (GC) can reclaim Strings once there are no active references.

What Happens When JSON is Converted to Objects (JsonNode, JsonObject)?

🚫 Converting JSON into a JsonNode or JsonObject before sending over the Event Bus:

  • Creates extra object representations in memory.

  • Increases garbage collection pressure.

  • Removes immutability benefits, leading to possible memory leaks.

Best Practice:

πŸš€ Pass raw JSON as a String between Verticles and defer transformation until needed by the final consumer.


2️⃣ Avoiding Multiple Copies When Passing JSON Over the Event Bus

When passing JSON across Vert.x Event Bus, we should ensure minimal memory overhead by leveraging Java's immutability.

Efficient JSON Transfer in Event Bus

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.Message;

public class JsonVerticle extends AbstractVerticle {
    @Override
    public void start() {
        vertx.eventBus().consumer("json.process", this::handleMessage);
    }

    private void handleMessage(Message<String> message) {
        // Forward the raw JSON string as-is, avoiding unnecessary transformations
        vertx.eventBus().send("json.next", message.body());
    }
}

βœ… String remains immutable and gets passed efficiently without duplication. βœ… Only the final consumer decides when transformation is needed.


3️⃣ Understanding String Garbage Collection in ApplicationScoped Verticles

How Application Scope Affects String Lifetime

Since ApplicationScoped Verticles in Quarkus live throughout the application's lifetime, their fields persist as long as the application is running. This means:

  • If a Verticle does not retain the string, the JSON string is eligible for GC once processing is complete.

  • If a Verticle stores the string in a field, it persists as long as the Verticle remains active, leading to memory retention issues.

  • If the Event Bus holds a reference, the string is not garbage collected until the last subscriber finishes processing.

Ensuring Efficient Memory Management

βœ… Do not store JSON strings as instance variables in Verticles. βœ… Use local variables instead, allowing them to be GC'd quickly. βœ… Use WeakReferences if retention is required without blocking GC. βœ… Monitor heap usage to track retained JSON strings.


4️⃣ Using Jackson and Mutiny to Stream JSON Efficiently

Even at the final consumer stage, we can avoid fully materializing JSON objects in memory. Instead of parsing everything into a JsonNode, we can use Jackson’s streaming API with Mutiny to process JSON fields on demand.

Efficient JSON Streaming with Jackson & Mutiny

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.smallrye.mutiny.Uni;
import java.io.StringReader;

public class JsonStreamingProcessor {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public Uni<String> extractField(String rawJson, String fieldName) {
        return Uni.createFrom().item(() -> {
            try (JsonParser parser = objectMapper.getFactory().createParser(new StringReader(rawJson))) {
                while (!parser.isClosed()) {
                    JsonToken token = parser.nextToken();
                    if (token != null && token.isStructStart()) {
                        continue; // Skip JSON struct start
                    }
                    if (token == JsonToken.FIELD_NAME && parser.getCurrentName().equals(fieldName)) {
                        parser.nextToken(); // Move to value
                        return parser.getValueAsString();
                    }
                }
                return "Field not found";
            }
        });
    }
}

βœ… Streams JSON instead of fully loading it into memory. βœ… Extracts only required fields, reducing overhead. βœ… Works efficiently in reactive pipelines using Mutiny.


5️⃣ Summary of Best Practices

StepBest Practice
Passing JSON in Event BusSend as immutable String instead of JsonNode or JsonObject.
Minimizing CopiesAvoid transformations before passing JSON between Verticles.
Managing GCDo not store JSON in instance variables in ApplicationScoped Verticles.
Final Stage ProcessingUse Jackson Streaming API + Mutiny instead of full object parsing.

Conclusion

πŸš€ Handling JSON efficiently in Quarkus with Jackson and Vert.x Event Bus is essential for high-performance, memory-optimized microservices. By:

  • Keeping JSON as Strings in intra-JVM communication,

  • Avoiding unnecessary conversions before passing JSON across the Event Bus, and

  • Using Mutiny and Jackson Streaming for memory-efficient JSON processing,

we ensure scalability, reduced memory overhead, and optimal GC behavior.

Implement these best practices in your Quarkus-based microservices and observe the performance improvements! πŸš€

Β