Starburst developer blog

The latest news from our users, engineers, writers, and product folks for all our peers and friends out there.

Want even more updates and information? Go to the Starburst company blog.


This is part 3 of the Bleeding edge Java series. Start at the introduction if you haven’t already.

Printing is the process of converting a stream of JSON tokens into JSON text. For our library, our printer only needs to print a single JSON token at a time. Our serializer already produces a stream of JSON tokens and the JDK has a large set of existing methods for managing streams.

What we need is a mapping function that can be passed to the JDK Stream’s map() method. We can define a Java interface for this:

public interface JsonPrinter
    CharSequence print(JsonToken jsonToken);

With an instance of this interface we can serialize from a Java object to a stream of JSON tokens to JSON text like this:

String jsonText = serializer.serialize(object)

The implementation for our printer is very straightforward. Note the code uses a StringUtils utility from the link at the end of this article:

CharSequence print(JsonToken jsonToken)
    return switch (jsonToken) {
        case NumberToken(var number) -> number.toString();
        case StringToken(var string) -> StringUtils.quoteAndEscape(string);
        case BooleanToken(var value) -> value ? "true" : "false";
        case NullToken __ -> "null";
        case BeginArrayToken __ -> "[";
        case EndArrayToken __ -> "]";
        case BeginObjectToken __ -> "{";
        case EndObjectToken __ -> "}";
        case ObjectNameToken(var name) -> StringUtils.quoteAndEscape(name) + ":";
        case ValueSeparatorToken __ -> ",";

Here again we take advantage of enhanced switch and pattern matching (see Serialization for more details). Additionally, we use the deconstruction feature of Record Patterns. With the first case statement, case NumberToken(var number), the compiler matches if the token is a number token and then extracts the number token’s value, binding it to the new variable number. It can be thought of as this Java code:

if (jsonToken instanceOf NumberToken) {
    var number = ((NumberToken) jsonToken).value();

Finally, notice that the switch statement does not need a default case. We have case statements for all possible implementations in the JsonToken sealed hierarchy. The Java compiler knows this and doesn’t require a default. If in the future we add a new JsonToken type this code would no longer compile.

Test it out for yourself!

In the prior article we developed a serializer that can serialize a Java record into a stream of JSON tokens. We now have a printer that can map a JSON token into JSON text. Let’s put this together in jshell. The example use these files:

From a terminal with Java 19 installed, run the following (note you’ll need the wget utility):

wget -nc
wget -nc
wget -nc
wget -nc
wget -nc
jshell --enable-preview

Inside jshell let’s serialize a Java record into JSON text:

var serializer = JsonSerializer.instance();
var printer = JsonPrinter.instance();

record Person(String name, int age) {}
var person = new Person("someone", 28);

String jsonText = serializer.serialize(person)  // serialize record to stream of tokens
    .map(printer::print)                        // map each JsonToken to a String (as a CharSequence)
    .collect(Collectors.joining());             // collect into a String


We can now serialize a Java record into JSON text. Let’s look at parsing into tokens next in parsing.

We’re hiring

Want to be able to use the latest features of Java? We’re hiring!

Jordan Zimmerman is a Senior Software Engineer working on Starburst Galaxy.