How to make Map expendible(?) safely in java I am building some statistic counter in Java.
Each metric could have flexible depth so the implementation should able to explore the depth when value insert or update.
So I implement like below (with Test)
public class TestRecursiveMap {

    private void recursiveRegister(Map&lt;String, Object&gt; target, String[] keys, int curr) {
        if(curr &gt; keys.length -1) return;
        String currKey = keys[curr];
        if(target.containsKey(currKey)) {
            Object currVal = target.get(currKey);
            if(curr &gt;= keys.length -1) {
                int value = (int) currVal;
                target.put(currKey, value + 1);
            } else {
                Map&lt;String, Object&gt; subMap = (Map&lt;String, Object&gt;)currVal;
                recursiveRegister(subMap, keys, curr + 1);
            }
        } else {
            if(curr &gt;= keys.length -1) {
                target.put(currKey, 1);
            } else {
                Map&lt;String, Object&gt; emptyMap = new HashMap&lt;&gt;();
                target.put(currKey, emptyMap);
                recursiveRegister(emptyMap, keys, curr + 1);
            }
        }
    }

    // result looks like
    // {a={b={c=1}}}
    // {a={b={c=2}}}
    @Test
    public void testRecursive(){
        Map&lt;String, Object&gt; originMap = new HashMap&lt;&gt;();
        String[] keys = {&quot;a&quot;, &quot;b&quot;, &quot;c&quot;};
        recursiveRegister(originMap, keys, 0);
        System.out.println(originMap);
        recursiveRegister(originMap, keys, 0);
        System.out.println(originMap);
    }
}

The code is so unsatisfied because of

casting in recursive logic - Map&lt;String, Object&gt; is very flexible but need to cast object to map or integer in every level

while casting there are bunch of possibilities of exceptions


How could I implement this safe and fast?
=====================================
Use case #1.
Actually this is an api counter. When you have an API call, it will counting the mount of the request.
For instance,
/api/user/1 and /api/v1/user/1 then it will have some value like below (in JSON form)
{
    &quot;api&quot; : {
        &quot;user&quot; : {
            &quot;1&quot; : 1
        },
        &quot;v1&quot; : {
            &quot;user&quot; : {
                &quot;1&quot; : 1
            }
        }
    }

}

So the basic logic is

if there is a key in the map, it will increase the number
if not, it will assign new k,v set and the initial value is 1
It took a long time just to understand what your program is doing and how it might be useful.
You were concerned about the unchecked casting, and that's a valid concern, but maybe it's not a problem if you make that behaviour clear, for instance, by including a failure scenario in the test case or JavaDoc, in which case you are just stating it's the responsibility of the other code that makes use of this to only pass valid arguments.
Here's a version of the same thing that's hopefully a bit better at describing what it does. With things a bit more plain to read, you can decide if you want your program to behave differently in the failure case -- maybe it should throw a checked exception instead of ClassCastException or maybe you want to actually support these cases?
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

/**
 * I am building some statistic counter in Java. Each metric could have flexible
 * depth so the implementation should able to explore the depth when value
 * insert or update.
 *
 */
public class StatisticsCounter {

    private Map&lt;String, Object&gt; rootMap = new HashMap&lt;&gt;();

    public void increment(String... paths) {
        recursiveIncrement(rootMap, paths, 0);
    }

    private void recursiveIncrement(Map&lt;String, Object&gt; targetMap, String[] pathToTarget,
            int pathIndex) {

        final int targetDepth = pathToTarget.length - 1;
        final String counterName = pathToTarget[pathIndex];

        if (pathIndex == targetDepth &amp;&amp; targetMap.containsKey(counterName)) {
            // assume objects at the target depth are int
            targetMap.put(counterName, (int) targetMap.get(counterName) + 1);
        } else if (pathIndex == targetDepth) {
            // at the target depth, put ints
            targetMap.put(counterName, 1);
        } else if (targetMap.containsKey(counterName)) {
            // assume object is a map, recur into it for the next counter name
            recursiveIncrement((Map&lt;String, Object&gt;) targetMap.get(counterName),
                    pathToTarget, pathIndex + 1);
        } else {
            // create a new map and recur into it for the next counter name
            Map&lt;String, Object&gt; emptyMap = new HashMap&lt;&gt;();
            targetMap.put(counterName, emptyMap);
            recursiveIncrement(emptyMap, pathToTarget, pathIndex + 1);
        }
    }

    public String toString() {
        return rootMap.toString();
    }

    @Test
    public void test() {
        StatisticsCounter counters = new StatisticsCounter();
        counters.increment(&quot;USA&quot;, &quot;California&quot;, &quot;Los Angeles&quot;);
        counters.increment(&quot;USA&quot;, &quot;California&quot;, &quot;Los Angeles&quot;);
        counters.increment(&quot;USA&quot;, &quot;California&quot;, &quot;San Diego&quot;);
        counters.increment(&quot;USA&quot;, &quot;California&quot;, &quot;Bay Area&quot;, &quot;San Francisco&quot;,
                &quot;Financial District&quot;);
        counters.increment(&quot;USA&quot;, &quot;Tennessee&quot;, &quot;Memphis&quot;);
        counters.increment(&quot;USA&quot;, &quot;Tennessee&quot;, &quot;Memphis&quot;);
        counters.increment(&quot;Italy&quot;, &quot;Venice&quot;);
        counters.increment(&quot;Italy&quot;, &quot;Rome&quot;);
        counters.increment(&quot;Italy&quot;, &quot;Venice&quot;);
        counters.increment(&quot;Vatican City&quot;);
        counters.increment(&quot;Vatican City&quot;);

        try {
            counters.increment(&quot;USA&quot;, &quot;California&quot;);
            fail(&quot;Cannot increment a key that already has subcounters&quot;);
        } catch (ClassCastException expectedException) {
            assertEquals(&quot;java.util.HashMap cannot be cast to java.lang.Integer&quot;,
                    expectedException.getMessage());
        }

        try {
            counters.increment(&quot;USA&quot;, &quot;Tennessee&quot;, &quot;Memphis&quot;, &quot;Graceland&quot;);
            fail(&quot;Cannot add subcounters to a key that already has a count&quot;);
        } catch (ClassCastException expectedException) {
            assertEquals(&quot;java.lang.Integer cannot be cast to java.util.Map&quot;,
                    expectedException.getMessage());
        }

        System.out.println(counters);
        // {USA={Tennessee={Memphis=2}, California={San Diego=1, Bay Area={San
        // Francisco={Financial District=1}}, Los Angeles=2}}, Italy={Rome=1, Venice=2},
        // Vatican City=2}
    }
}