Java's options for options

by: Ethan McCue

Hypothetical 1

Imagine that you made a bit of code that outputs JSON.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json
    ) {
        ...
    }
}

By default your output contains no extra whitespace, but you want to provide an option to the user to print that JSON with some indentation.

Without indentation

[{"name":"joe","age":35}]

With indentation

[
    {
        "name":"joe",
        "age":35
    }
]

Option 1. Don't support it

Any toggles you add to your api are toggles you might need to support now and forever more. Depending on the code you are writing and who its consumers are, it might make more sense to provide a more restricted api.

Option 2. Make another method

With only a single option you want configurable, you can just expose a method with a different name.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonWithIndentation(
            Appendable out,
            Json json
    ) {
        ...
    }
}
writeJson(out, json);
writeJsonWithIndentation(out, json);

Option 3. Add a boolean argument

A single option is either on or off. True or false. That is often the domain of a boolean.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json,
            boolean indent
    ) {
        if (indent) {
            ...
        }
        else {
            ...
        }
    }
}
writeJson(out, json, true);
writeJson(out, json, false);

Option 4. Add an enum argument

Booleans are great, but for understandability at the call-site you might want to provide an enum with two possible values instead.

public enum Indentation {
    INDENT,
    DO_NOT_INDENT
}

...

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json,
            Indentation indent
    ) {
        switch (indent) {
            case INDENT -> ...
            case DO_NOT_INDENT -> ...
        }
    }
}
writeJson(out, json, Indentation.INDENT);
writeJson(out, json, Indentation.DO_NOT_INDENT);

Hypothetical 2

Say now you get some feedback that while the indentation style is great for objects, it is sometimes not great for JSON with long arrays.

No indentation

[{"numbers":[1,2,3]}]

Indent Everything

[
  {
    "numbers": [
        1,
        2,
        3
    ]
  }
]

Indent Objects

[{
    "numbers": [1, 2, 3]
}]

Indent Arrays

[
  {"numbers": [
        1,
        2,
        3
  ]}
]

Option 5. Make methods for requested combinations

Your users just want a way to turn off indentation for arrays. Give it to them.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentObjects(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentEverything(
            Appendable out,
            Json json
    ) {
        ...
    }
}
writeJson(out, json);
writeJsonIndentObjects(out, json);
writeJsonIndentEverything(out, json);

Option 6. Make methods for every combination

There are four logical settings that come out of two different flags, so you can certainly provide all four options as methods. Could save you time later.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentObjects(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentArrays(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentEverything(
            Appendable out,
            Json json
    ) {
        ...
    }
}
writeJson(out, json);
writeJsonIndentObjects(out, json);
writeJsonIndentArrays(out, json);
writeJsonIndentEverything(out, json);

Option 7. Have two boolean arguments

Two logically independent things to configure, you can always take a boolean for each.

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json,
            boolean indentObjects,
            boolean indentArrays
    ) {
        if (indentObjects) {
            if (indentArrays) {
                ...
            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(out, json, true, true);
writeJson(out, json, true, false);
writeJson(out, json, false, true);
writeJson(out, json, false, false);

Option 8. Have two enum arguments

Booleans describe everything, but enums are still more explicit.

public enum Indentation {
    INDENT,
    DO_NOT_INDENT
}

...

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json,
            Indentation indentObjects,
            Indentation indentArrays
    ) {
        switch (indentObjects) {
            case INDENT -> switch (indentArrays) {
                case INDENT -> ...
                case DO_NOT_INDENT -> ...
            }
            case DO_NOT_INDENT -> ...
        }
    }
}
writeJson(out, json, Indentation.INDENT, Indentation.INDENT);
writeJson(out, json, Indentation.INDENT, Indentation.DO_NOT_INDENT);
writeJson(out, json, Indentation.DO_NOT_INDENT, Indentation.INDENT);
writeJson(
        out, 
        json, 
        Indentation.DO_NOT_INDENT, 
        Indentation.DO_NOT_INDENT
);

Option 9. Take options as bit flags

It's an old school solution and maybe a bit too clever, but you are feeling old school and clever.

public final class Indentation {
    public static final int NO_INDENTATION = 0b00;
    public static final int INDENT_OBJECTS = 0b01;
    public static final int INDENT_ARRAYS = 0b10;
    
    private Indentation() {}
}

...

public final class JsonWriter {
    private JsonWriter() {}
    
    public static void writeJson(
            Appendable out,
            Json json,
            int indentation
    ) {
        if ((indentation & Indentation.INDENT_OBJECTS) != 0) {
            if ((indentation & Indentation.INDENT_ARRAYS) != 0) {
                ...
            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(
        out, 
        json, 
        Indentation.INDENT_OBJECTS | Indentation.INDENT_ARRAYS, 
);
writeJson(out, json, Indentation.INDENT_OBJECTS);
writeJson(out, json, Indentation.INDENT_ARRAYS);
writeJson(out, json, Indentation.NO_INDENTATION);

Option 10. Take an EnumSet

Rather than waste a parameter on each flag, explicitly take the set of behaviors they want to enable.

public enum Indent {
    OBJECTS,
    ARRAYS
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent
    ) {
        if (indent.contains(Indent.OBJECTS)) {
            if (indent.contains(Indent.ARRAYS)) {
                ...
            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(out, json, EnumSet.of(Indent.OBJECTS, Indent.ARRAYS));
writeJson(out, json, EnumSet.of(Indent.OBJECTS));
writeJson(out, json, EnumSet.of(Indent.ARRAYS));
writeJson(out, json, EnumSet.noneOf(Indent.class));

Option 11. Take a transparent config object

Similar to just taking two booleans, but putting them in an object means you can refer to a set of options as a concrete "thing". This could help you keep the most common usages terse.

record Options(boolean indentObjects, boolean indentArrays) {
    public static final Options INDENT_EVERYTHING =
            new Options(true, true);
    public static final Options NO_INDENT =
            new Options(false, false);
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Options options
    ) {
        if (options.indentObjects()) {
            if (options.indentArrays()) {
                ...
            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(out, json, Options.INDENT_EVERYTHING);
writeJson(out, json, new Options(true, false));
writeJson(out, json, new Options(false, true));
writeJson(out, json, Options.NO_INDENT);

Option 12. Take an opaque config object

Maybe you want to give your api some extra wiggle room to grow. Maybe you just like how the usage of an opaque object made from a builder looks.

With this approach you can choose to internally represent things as booleans, enums, an enum set, bit flags, or whatever other evil lies within the hearts of mankind.

public final class Options {
    private final boolean indentObjects;
    private final boolean indentArrays;

    private Options(Builder builder) {
        this.indentArrays = builder.indentArrays;
        this.indentObjects = builder.indentObjects;
    }

    public boolean indentArrays() {
        return this.indentArrays;
    }

    public boolean indentObjects() {
        return this.indentObjects;
    }

    public static Options standard() {
        return builder().build();
    }
    
    public static Builder builder() {
        return new Builder();
    }

    public final class Builder {
        private boolean indentObjects;
        private boolean indentArrays;

        private Builder() {
            this.indentObjects = false;
            this.indentArrays = false;
        }
        
        public Builder indentObjects() {
            this.indentObjects = true;
            return this;
        }
        
        public Builder indentArrays() {
            this.indentArrays = true;
            return this;
        }
        
        public Options build() {
            return new Options(this);
        }
    }
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Options options
    ) {
        if (options.indentObjects()) {
            if (options.indentArrays()) {
                ...
            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .indentArrays()
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentArrays()
        .build()
);
writeJson(out, json, Options.standard());

Hypothetical 3.

🚑 Weewoo Weewoo 🚑

Its legal! Your apis are great and all, but when you send data to external clients we would really like to include an explicit statement of copyright. That copyright message might change depending on your contract with the client and also we shouldn't send it internally.

Good luck, legal out.

[
    {
        "name": "joe",
        "age": 35
    }
]
{ 
    "copyright": "(c) 2022 Inc.",
    "data": [
        {
            "name":"joe",
            "age":35
        }
    ]
}

Option 13. Add 4 more methods to hit the new combinations

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentObjects(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentArrays(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonIndentEverything(
            Appendable out,
            Json json
    ) {
        ...
    }

    public static void writeJsonWithCopyright(
            Appendable out,
            Json json,
            String copyright
    ) {
        ...
    }

    public static void writeJsonIndentObjectsWithCopyright(
            Appendable out,
            Json json,
            String copyright
    ) {
        ...
    }

    public static void writeJsonIndentArraysWithCopyright(
            Appendable out,
            Json json,
            String copyright
    ) {
        ...
    }

    public static void writeJsonIndentEverythingWithCopyright(
            Appendable out,
            Json json,
            String copyright
    ) {
        ...
    }
}
writeJson(out, json);
writeJsonIndentObjects(out, json);
writeJsonIndentArrays(out, json);
writeJsonIndentEverything(out, json);
writeJsonWithCopyright(out, json, "(c) 2022");
writeJsonIndentObjectsWithCopyright(out, json, "(c) 2022");
writeJsonIndentArraysWithCopyright(out, json, "(c) 2022");
writeJsonIndentEverythingWithCopyright(out, json, "(c) 2022");

Options 14. Add a single new method

If your boolean-like options only took up a single overload, you can get away with just adding a single new method to the list.

This will look different depending on whether you used booleans, enums, an EnumSet, or bit flags.

public enum Indent {
    OBJECTS,
    ARRAYS
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent
    ) {
        
    }

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent,
            String copyright
    ) {

    }
}
writeJson(out, json, EnumSet.of(Indent.OBJECTS, Indent.ARRAYS));
writeJson(out, json, EnumSet.of(Indent.OBJECTS));
writeJson(out, json, EnumSet.of(Indent.ARRAYS));
writeJson(out, json, EnumSet.noneOf(Indent.class));
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.OBJECTS, Indent.ARRAYS),
    "(c) 2022"
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.OBJECTS),
    "(c) 2022"
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.ARRAYS),
    "(c) 2022"
);
writeJson(
    out, 
    json, 
    EnumSet.noneOf(Indent.class), 
    "(c) 2022"
);

Option 15. Add another argument and accept null

If you don't want to add yet another overload, you can always just allow users to pass null.

public enum Indent {
    OBJECTS,
    ARRAYS
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent,
            String copyright
    ) {
        if (indent.contains(Indent.OBJECTS)) {
            if (indent.contains(Indent.ARRAYS)) {
                if (copyright == null) {
                    ...
                }
                else {
                    ... 
                }

            }
            else {
                ...
            }
        }
        else {
            ...
        }
    }
}
writeJson(out, json, EnumSet.of(Indent.OBJECTS, Indent.ARRAYS), null);
writeJson(out, json, EnumSet.of(Indent.OBJECTS), null);
writeJson(out, json, EnumSet.of(Indent.ARRAYS), null);
writeJson(out, json, EnumSet.noneOf(Indent.class), null);
writeJson(
        out,
        json,
        EnumSet.of(Indent.OBJECTS, Indent.ARRAYS), 
        "(c) 2022"
);
writeJson(out, json, EnumSet.of(Indent.OBJECTS), "(c) 2022");
writeJson(out, json, EnumSet.of(Indent.ARRAYS), "(c) 2022");
writeJson(out, json, EnumSet.noneOf(Indent.class), "(c) 2022");

Option 16. Add another argument and make it be an Optional

It's time to paint a bike shed. You can do this with java.util.Optional or your own sealed type.

public enum Indent {
    OBJECTS,
    ARRAYS
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent,
            Optional<String> copyright
    ) {
        if (indent.contains(Indent.OBJECTS)) {
            if (indent.contains(Indent.ARRAYS)) {
                if (copyright.isPresent()) {
                    ... copyright.orElseThrow() ...
                } else {
                    ...
                }
            } else {
                ...
            }
        } else {
            ...
        }
    }
}
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.OBJECTS, Indent.ARRAYS), 
    Optional.empty()
);
writeJson(
    out,
    json, 
    EnumSet.of(Indent.OBJECTS),
    Optional.empty()
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.ARRAYS),
    Optional.empty()
);
writeJson(
    out, 
    json, 
    EnumSet.noneOf(Indent.class), 
    Optional.empty()
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.OBJECTS, Indent.ARRAYS), 
    Optional.of("(c) 2022")
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.OBJECTS), 
    Optional.of("(c) 2022")
);
writeJson(
    out, 
    json, 
    EnumSet.of(Indent.ARRAYS), 
    Optional.of("(c) 2022")
);
writeJson(
    out, 
    json, 
    EnumSet.noneOf(Indent.class), 
    Optional.of("(c) 2022")
);

Option 17. Add a nullable property to a transparent config object

record Options(
        boolean indentObjects, 
        boolean indentArrays,
        String copyright) {
    public static final Options INDENT_EVERYTHING =
            new Options(true, true, null);
    public static final Options NO_INDENT =
            new Options(false, false, null);
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Options options
    ) {
        if (options.indentObjects()) {
            if (options.indentArrays()) {
                if (options.copyright() == null) {
                    ...
                }
                else {
                    ...   
                }
            }
            else {
                ... 
            }
        }
        else { 
            ... 
        }
    }
}

Option 18. Add an Optional property to a transparent config object

Same as above, but if you like to explicitly have the property be optional you can, but if you are using records as your transparent data carriers then you need to take an Optional in your constructor.

record Options(
        boolean indentObjects,
        boolean indentArrays,
        Optional<String> copyright
) {
    public static final Options INDENT_EVERYTHING =
            new Options(true, true, Optional.empty());
    public static final Options NO_INDENT =
            new Options(false, false, Optional.empty());
}

Option 19. Add another property to an opaque config object

public final class Options {
    private final boolean indentObjects;
    private final boolean indentArrays;
    private final String copyright;

    private Options(Builder builder) {
        this.indentArrays = builder.indentArrays;
        this.indentObjects = builder.indentObjects;
        this.copyright = builder.copyright;
    }

    public boolean indentArrays() {
        return this.indentArrays;
    }

    public boolean indentObjects() {
        return this.indentObjects;
    }

    public Optional<String> copyright() {
        return Optional.ofNullable(this.copyright);
    }

    public static Options standard() {
        return builder().build();
    }

    public static Builder builder() {
        return new Builder();
    }

    public final class Builder {
        private boolean indentObjects;
        private boolean indentArrays;
        private String copyright;

        private Builder() {
            this.indentObjects = false;
            this.indentArrays = false;
            this.copyright = null;
        }

        public Builder indentObjects() {
            this.indentObjects = true;
            return this;
        }

        public Builder indentArrays() {
            this.indentArrays = true;
            return this;
        }

        public Builder copyright(String copyright) {
            this.copyright = copyright;
            return this;
        }
        
        public Options build() {
            return new Options(this);
        }
    }
}
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .indentArrays()
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentArrays()
        .build()
);
writeJson(out, json, Options.standard());
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .indentArrays()
        .copyright("(c) 2022")
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentObjects()
        .copyright("(c) 2022")
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .indentArrays()
        .copyright("(c) 2022")
        .build()
);
writeJson(
    out, 
    json, 
    Options.builder()
        .copyright("(c) 2022")
        .build()
);

Hypothetical 4.

It would be a lot more efficient if you started sending your JSON in a binary format like MessagePack. It has the same data model as JSON, so it should work out.

Also, when sending in that binary format there is a choice between "Little Endian" and "Big Endian".

Problem is, there really isn't a meaning to indentation in a binary format or to endianness in a text one.

Option 20. Make separate methods

For a split as fundamental as this, it might make sense to start to make an entirely separate API for the new JSON-like format.

public enum Indent {
    OBJECTS,
    ARRAYS
}

...
        
enum Endianness {
    BIG_ENDIAN,
    LITTLE_ENDIAN
}
  
...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            EnumSet<Indent> indent,
            Optional<String> copyright
    ) {
        ...
    }
    
    public static void writeMessagePack(
            Appendable out,
            Json json,
            Endianness endianness,
            Optional<String> copyright
    ) {
        ...
    }
}
writeJson(
        out, 
        json,
        EnumSet.of(Indent.OBJECTS),
        Optional.of("(c) 2022")
);
writeMessagePack(
        out,
        json,
        Endianness.BIG_ENDIAN,
        Optional.of("(c) 2022")
);

Option 21. Make an interface and use dispatch

You were surprised you didn't think of this first. Dynamic dispatch is some classic Java stylings.

public interface JsonWriter {
    void write(Appendable out, JSON json);
}

...

public final class TextJsonWriter implements JsonWriter {
    private final boolean indentObjects;
    private final boolean indentArrays;
    private final String copyright;
    
    public TextJsonWriter(
            boolean indentObjects, 
            boolean indentArrays,
            String copyright
    ) {}
    
    @Override
    public void write(Appendable out, JSON json) {
        ...
    }
}

...

enum Endianness {
    BIG_ENDIAN,
    LITTLE_ENDIAN
}

...

public final class BinaryJsonWriter implements JsonWriter {
    private final Endianness endianness;
    private final String copyright;

    public BinaryJsonWriter(
            Endianness endianness,
            String copyright
    ) {}
    
    @Override
    public void write(Appendable out, JSON json) {
        ...
    }
}
new BinaryJsonWriter(
        Endianness.BIG_ENDIAN,
        "(c) 2022"
).writeJson(out, json);

new TextJsonWriter(
        true,
        false,
        "(c) 2022"
).writeJson(out, json);

Option 22. Take everything as an object and figure it out at runtime.

You need to choose whether you silently ignore bad combinations of objects and what behaviors get preference, but there is a simplicity to just throwing it all into a record or opaque object and figuring it out from there.

enum Endianness {
    BIG_ENDIAN,
    LITTLE_ENDIAN
}

record Options(
        Boolean indentObjects, 
        Boolean indentArrays,
        String copyright,
        boolean useBinary,
        Endianness endianness
) {
    public static final Options INDENT_EVERYTHING =
            new Options(
                    true, 
                    true, 
                    null, 
                    false, 
                    null
            );
    public static final Options NO_INDENT =
            new Options(
                    false, 
                    false, 
                    null, 
                    false, 
                    null
            );
    public static final Options BINARY_LE = 
            new Options(
                    null,
                    null, 
                    null, 
                    true, 
                    Endianness.LITTLE_ENDIAN
            );
}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Options options
    ) {
        if (options.useBinary() &&
                (options.indentArrays() != null 
                        || options.indentObjects() != null)) {
            // ignore or throw
            ...
        }
        else if (!options.useBinary() &&
                    options.endianness() != null) {
            ...
        }
        else {
            ...
        }
    }
}
writeJson(
        out, 
        json, 
        Options.INDENT_EVERYTHING
);
writeJson(
        out,
        json,
        Options.BINARY_LE
);
writeJson(
        out,
        json,
        new Options(
            null,
            null, 
            null,
            true, 
            Endianness.BIG_ENDIAN
        )
);

Option 23. Model valid choices in the type hierarchy.

With a bit of restructuring, you can actually make an Options object that will correctly handle having that disjoint set of options.

Maybe not what you would choose with 100 settings or more complicated legality restrictions, but for this case it all seems to work out.

enum Endianness {
    BIG_ENDIAN,
    LITTLE_ENDIAN
}

sealed interface Options permits BinaryOptions, TextOptions {
    Optional<String> copyright();
}

record TextOptions(
        @Override Optional<String> copyright,
        boolean indentObjects,
        boolean indentArrays
) implements Options {}

record BinaryOptions(
        @Override Optional<String> copyright,
        Endianness endianness
) implements Options {}

...

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Options options
    ) {
        switch (options) {
            case TextOptions textOptions -> {
                ...
            }
            case BinaryOptions binaryOptions ->
                switch (binaryOptions.endianness()) {
                    case BIG_ENDIAN -> ...
                    case LITTLE_ENDIAN -> ...
                }
        }
    }
}
writeJson(
        out, 
        json, 
        new TextOptions(Optional.of("(c) 2022"), true, false)
);
writeJson(
        out,
        json,
        new BinaryOptions(Optional.empty(), Endianness.BIG_ENDIAN)
);

Option 24. Give up on typing it, just pass a map

This was always an option. It works just as well here as it does in a dynamic language, it's just a tad more verbose and unsafe.

public final class JsonWriter {
    private JsonWriter() {}

    public static void writeJson(
            Appendable out,
            Json json,
            Map<String, Object> options
    ) {
        var copyright = options.get("copyright");
        if (copyright == null) {
            ...
            var endianness = options.get("binary");
            ...
        }
        else if (copyright instanceof String copyrightString) {
            ...
        }
        else {
            throw new IllegalArgumentException(...);
        }
    }
}
writeJson(
        out, 
        json,
        Map.of(
            "indentObjects", true,
            "copyright", "(c) 2022"
        )
);
writeJson(
        out, 
        json,
        Map.of(
            "binary", true,
            "endianness", Endianness.BIG_ENDIAN
        )
);

Hypothetical 5.

You've taken the mouse to the movies. People don't need all the configuration options you've provided and don't like using the API that has them. They want a simpler API.

Maybe you should have gone with option 1.


Exercise for the reader. Make a spreadsheet of all these options versus all the criteria you use to evaluate software and fill in the grid with smiley faces, frowny faces, and meh faces. Feel free to fill in some options I missed, like reading from environment variables, system properties, or more inheritance schemes.


<- Index