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
NamedTupleinstead - 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<>();
Updating the Count (Java)
usage
.computeIfAbsent("prod", e -> new HashMap<>())
.computeIfAbsent("us-east", r -> new HashMap<>())
.merge("dark_mode", 1, Integer::sum);
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<>();
Now the key contains all three dimensions:
Tuple key = Tuple.of("prod", "us-east", "dark_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
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
Much cleaner than nested maps.
Before vs After
Nested Map Structure
Map<String, Map<String, Map<String, Integer>>> usage;
Flat Structure
Map<Tuple, Integer> usage;
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");
You may prefer:
NamedTuple.of(Map.of(
"env", "prod",
"region", "us-east",
"feature", "dark_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
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>>>
You get:
Map<Tuple, V>
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)