Java generics with wildcard vs non generic List to deal with non generic data
@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;
}