Form every unique combination of the items of a list of lists
This seems more complicated than it needs to be.  Consider the following code 

        int currentIndex = current.size();
        for (String s : factors.get(currentIndex)) {
            current.add(s);
            multiply(factors, current, results);
            current.remove(currentIndex);
        }


This code adds and removes an element to the current list.  In between, it calls itself recursively.  Then consider 

        if (current.size() &gt;= factors.size()) {
            // Don't really need to make a deeper copy with String values
            // but might as well in case we change types later.
            List&lt;String&gt; result = new ArrayList&lt;&gt;();
            for (String s : current) {
                result.add(new String(s));
            }
            results.add(result);

            return;
        }


When we find that we've processed an entire result, we add to the results and return.  

Note that we could optimize that a bit by switching from a deep copy to a shallow copy.  With strings, that shouldn't make a difference.  I did the deep copy because we aren't restricted to strings with this kind of operation.  

        if (current.size() &gt;= factors.size()) {
            results.add(new ArrayList&lt;&gt;(current));

            return;
        }


That's the shallow version.  

The whole thing:  

    private static void multiply(List&lt;List&lt;String&gt;&gt; factors, List&lt;String&gt; current, List&lt;List&lt;String&gt;&gt; results) {
        if (current.size() &gt;= factors.size()) {
            // Don't really need to make a deeper copy with String values
            // but might as well in case we change types later.
            List&lt;String&gt; result = new ArrayList&lt;&gt;();
            for (String s : current) {
                result.add(new String(s));
            }
            results.add(result);

            return;
        }

        int currentIndex = current.size();
        for (String s : factors.get(currentIndex)) {
            current.add(s);
            multiply(factors, current, results);
            current.remove(currentIndex);
        }
    }

    private static List&lt;List&lt;String&gt;&gt; multiply(List&lt;List&lt;String&gt;&gt; factors) {
        List&lt;List&lt;String&gt;&gt; results = new ArrayList&lt;&gt;();
        multiply(factors, new ArrayList&lt;String&gt;(), results);
        return results;
    }


I added the second method so that I could call it like 

        System.out.println(Arrays.toString(multiply(group).toArray()));


Otherwise I would have needed to use more lines in the caller.  

This version outputs the products in the same order as shown in your question, where your code outputs them in reverse order.  I don't know if this matters or not.  

I find this to be more straightforward than your version.  It builds up to the answer.  Your version builds the answer on its way back from the recursion, which is why the results are backwards.  

It also avoids some things that I didn't like about your version:  


            for (List&lt;String&gt; string : subList) {



I'm not sure exactly what it is, but it's not a string.  It's a List.  I'd have probably called it something like strings or pieces in lieu of a better name.  


                        result2.add(tmp2);



I'm not a fan of numbered variables.  I find it especially confusing here, as result and result2 are the same type but tmp and tmp2 are not.  

I'm not crazy about your use of tmp for a loop index variable.  It seems like there should have been a better name.  That variable is no more temporary than any other.  


            List&lt;String&gt; result = new ArrayList&lt;String&gt;();
            for (List&lt;String&gt; string : subList) {
                result = new ArrayList&lt;String&gt;();



You could just say 

            for (List&lt;String&gt; string : subList) {
                List&lt;String&gt; result = new ArrayList&lt;&gt;();


The first initialization doesn't do anything before it is overwritten and result is never used beyond a single loop iteration.