Java generics with wildcard vs non generic List to deal with non generic data There is a problem on Exercism Java track that requires writing a function to flatten an arbitrarily nested list to a flattened list without null values.
For Example
input: [1,[2,3,null,4],[null],5]
output: [1,2,3,4,5]
Here is the solution I wrote for it without using wildcards.
public List flatten(List&lt;Object&gt; two) {
    Function&lt;Object, List&gt; mapper = item -&gt; (item instanceof List) ? flatten((List) item) : Collections.singletonList(item);
    return (List) two.stream()
            .filter(x -&gt; x != null)
            .map(mapper)
            .flatMap(x -&gt; x.stream())
            .collect(Collectors.toList());
}

Here is my solution with the wildcard generics. My IDE was happier with this solution.
public List&lt;Object&gt; flatten(List&lt;?&gt; two) {
    Function&lt;Object, List&lt;?&gt;&gt; mapper = item -&gt; (item instanceof List) ? flatten((List&lt;?&gt;) item) : Collections.singletonList(item);
    return two.stream()
            .filter(Objects::nonNull)
            .map(mapper)
            .flatMap(List::stream)
            .collect(Collectors.toList());
}

Is it ok to use the wildcard in this case? Which solution would you prefer? Any other comments about the code are also welcome.
@TorbenPutkonen already provided a great review, I just wanted to add something about usability.
Depending on your use case, returning a List&lt;Object&gt; means that you need to do the casting later. For example:
List&lt;Object&gt; result = flatten(myList);
for(Object o : result) {
    if(o instanceof String) {
        String myString = (String) o;
        // do something with the string
    }
    // etc...
}

If you know the type of the elements you can pass it to the method:
@SuppressWarnings(&quot;unchecked&quot;)
public &lt;T&gt; List&lt;T&gt; flatten(List&lt;?&gt; list, Class&lt;T&gt; itemType) {
    return list.stream()
            .filter(Objects::nonNull)
            .flatMap(i -&gt; i instanceof List ? flatten((List&lt;?&gt;)i, itemType).stream() : Stream.of((T)i))
            .collect(Collectors.toList());
}

And then use it like:
List&lt;String&gt; result = flatten(myList, String.class);
for(String s : result) {
    // do something with the string
}

This is the version without streams:
@SuppressWarnings(&quot;unchecked&quot;)
public static &lt;T&gt; List&lt;T&gt; flatten(List&lt;?&gt; list, Class&lt;T&gt; itemType) {
    List&lt;T&gt; result = new ArrayList&lt;&gt;();
    for(Object o : list) {
        if(o instanceof List) {
            result.addAll(flatten((List&lt;?&gt;) o, itemType));
        }
        else if (o != null){
            result.add((T) o);
        }
    }
    return result;
}