DEV Community

Robin Alex Panicker
Robin Alex Panicker

Posted on

Replacing Nested Maps with Tuple Keys in Java and Kotlin

We at Appxiom built a simple but useful library for Java and Kotlin.

AxTuple (ax-tuple-java) is a MIT Licensed lightweight library available as Maven library and Gradle plugin.

Let's try AxTuple in a real-world scenario where we have a deeply nested HashMap structure. The goal is simple: reduce complexity without introducing unnecessary DTOs.

AxTuple is extremely practical for flattening multi-layer maps into clean, readable composite keys - while remaining immutable and safe to use in hash-based collections.

In this post, I’ll walk through:

  • The problem with nested maps
  • The clean alternative using Tuple
  • When to use NamedTuple instead
  • Java and Kotlin examples
  • Practical observations after testing

The Problem: Nested HashMap

Let’s say we are tracking feature usage in an application.

We want to count how many times a feature is used based on:

  • Environment (prod, staging)
  • Region (us-east, eu-west)
  • Feature name (dark_mode, search_v2)

The naive structure looks like this:

Map<String, Map<String, Map<String, Integer>>> usage = new HashMap<>();
Enter fullscreen mode Exit fullscreen mode

Updating the Count (Java)

usage
    .computeIfAbsent("prod", e -> new HashMap<>())
    .computeIfAbsent("us-east", r -> new HashMap<>())
    .merge("dark_mode", 1, Integer::sum);
Enter fullscreen mode Exit fullscreen mode

This works — but:

  • Hard to read
  • Hard to debug
  • Null-safety complexity
  • Deep nesting
  • Ugly generics
  • Painful refactoring

After testing this approach in a medium-sized codebase, it quickly became clear that the structure itself was the problem.


The Clean Solution: Flatten with Tuple Keys

Instead of nesting maps, we flatten everything.

One key. One map.

Map<Tuple, Integer> usage = new HashMap<>();
Enter fullscreen mode Exit fullscreen mode

Now the key contains all three dimensions:

Tuple key = Tuple.of("prod", "us-east", "dark_mode");
Enter fullscreen mode Exit fullscreen mode

Java Example

import com.appxiom.ax.tuple.Tuple;
import java.util.HashMap;
import java.util.Map;

Map<Tuple, Integer> usage = new HashMap<>();

Tuple key = Tuple.of("prod", "us-east", "dark_mode");

// Increment count
usage.merge(key, 1, Integer::sum);

// Increment again
usage.merge(key, 1, Integer::sum);

System.out.println(usage.get(Tuple.of("prod", "us-east", "dark_mode")));
// Output: 2
Enter fullscreen mode Exit fullscreen mode

Why This Works

AxTuple implements:

  • Proper equals()
  • Proper hashCode()
  • Immutability

So even if you create a new Tuple instance with the same values, it still resolves correctly in the HashMap.

I verified this by:

  • Creating keys in one method
  • Looking them up in another
  • Reconstructing keys dynamically

No collisions. No surprises.


Kotlin Version

import com.appxiom.ax.tuple.Tuple

val usage = mutableMapOf<Tuple, Int>()

val key = Tuple.of("prod", "us-east", "dark_mode")

usage.merge(key, 1, Int::plus)
usage.merge(key, 1, Int::plus)

println(usage[Tuple.of("prod", "us-east", "dark_mode")])
// Output: 2
Enter fullscreen mode Exit fullscreen mode

Much cleaner than nested maps.


Before vs After

Nested Map Structure

Map<String, Map<String, Map<String, Integer>>> usage;
Enter fullscreen mode Exit fullscreen mode

Flat Structure

Map<Tuple, Integer> usage;
Enter fullscreen mode Exit fullscreen mode

The second one is:

  • Easier to understand
  • Easier to pass around
  • Easier to serialize
  • Easier to test

And most importantly: less cognitive load.


NamedTuple Is Even Better

Tuple works great when order is clear.

But sometimes readability matters more than position.

Instead of:

Tuple.of("prod", "us-east", "dark_mode");
Enter fullscreen mode Exit fullscreen mode

You may prefer:

NamedTuple.of(Map.of(
    "env", "prod",
    "region", "us-east",
    "feature", "dark_mode"
));
Enter fullscreen mode Exit fullscreen mode

Java Example with NamedTuple

import com.appxiom.ax.tuple.NamedTuple;
import java.util.HashMap;
import java.util.Map;

Map<NamedTuple, Integer> usage = new HashMap<>();

NamedTuple key = NamedTuple.of(Map.of(
    "env", "prod",
    "region", "us-east",
    "feature", "dark_mode"
));

usage.merge(key, 1, Integer::sum);

NamedTuple lookup = NamedTuple.of(Map.of(
    "env", "prod",
    "region", "us-east",
    "feature", "dark_mode"
));

System.out.println(usage.get(lookup));
// Output: 1
Enter fullscreen mode Exit fullscreen mode

Why I Liked NamedTuple

  • Debug logs are clearer
  • Keys are self-documenting
  • Refactoring is safer
  • Order mistakes are impossible

The only tradeoff is slightly more verbosity.


Observations

After testing with:

  • ~100K inserts
  • Repeated lookups
  • Reconstructed key objects

Flat HashMaps with Tuple keys performed much faster when looping through.

What improved dramatically was:

  • Code readability
  • Maintainability
  • Refactoring safety
  • Test simplicity

When Should You Use AxTuple?

Use it when:

  • You have multi-dimensional map keys
  • You are building analytics counters
  • You want to avoid nested map hell
  • You don’t want to create a one-off composite key class
  • You want immutable composite keys

Avoid it when:

  • You need domain-rich behavior (then create a proper class)
  • Your key semantics are complex and deserve explicit modeling

Thoughts

After testing AxTuple in a real nested-map scenario, I can confidently say:

Flattening multi-layer HashMaps with Tuple keys significantly simplifies design.

Instead of:

Map<A, Map<B, Map<C, V>>>
Enter fullscreen mode Exit fullscreen mode

You get:

Map<Tuple, V>
Enter fullscreen mode Exit fullscreen mode

Cleaner.
More maintainable.
Less boilerplate.

And because AxTuple is immutable and hash-safe, it behaves exactly how a composite key should.

If you are dealing with multi-dimensional counters, caches, or analytics maps - this pattern is worth adopting.

Sometimes the best refactor isn’t adding a new abstraction.

It’s removing unnecessary structure.

For more on AxTuple - visit https://github.com/basilgregory/ax-tuple-java

Top comments (0)