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
Step | Best Practice |
Passing JSON in Event Bus | Send as immutable String instead of JsonNode or JsonObject . |
Minimizing Copies | Avoid transformations before passing JSON between Verticles. |
Managing GC | Do not store JSON in instance variables in ApplicationScoped Verticles. |
Final Stage Processing | Use 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! π