Initializing local variable to generic interface
This is as safe as your Set implementation for result. For example, swapping for a TreeSet gave the following error:

Exception in thread "main" java.lang.ClassCastException: 
            java.lang.Integer cannot be cast to java.lang.Double
    at java.lang.Double.compareTo(Double.java:49)
    at java.util.TreeMap.put(TreeMap.java:568)
    at java.util.TreeSet.add(TreeSet.java:255)
    at java.util.AbstractCollection.addAll(AbstractCollection.java:344)
    at java.util.TreeSet.addAll(TreeSet.java:312)


This is understandable since TreeSet imposes an ordering, which in turns calls the to-be-added Collection-type's compareTo() with the existing entries. This has nothing to do with type incompatibility at compile-time (the code compiles), rather it's a limitation/feature of the TreeSet implementation.

In your linked question @rolfl suggested giving a Supplier to the method, which I will advocate here too:

public class TypeInference {

    public static &lt;E&gt; Set&lt;E&gt; union (Set&lt;? extends E&gt; s1, Set&lt;? extends E&gt; s2, 
            Supplier&lt;Set&lt;E&gt;&gt; supplier) {
        Set&lt;E&gt; result = supplier.get();
        result.addAll(s1);
        result.addAll(s2);
        return result;
    }

    public static &lt;E&gt; Set&lt;E&gt; union (Set&lt;? extends E&gt; s1, Set&lt;? extends E&gt; s2) {
        return union(s1, s2, () -&gt; new TreeSet&lt;&gt;(Comparator.comparing(Object::toString)));
    }

    public static void main(String[] args) {
        Set&lt;Integer&gt; ints = new TreeSet&lt;&gt;(Arrays.asList(1, 2, 3, 4, 5));
        Set&lt;Double&gt; doubles = new TreeSet&lt;&gt;(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0));
        System.out.println(union(ints, doubles));
        System.out.println(union(ints, doubles, 
                () -&gt; new TreeSet&lt;&gt;(Comparator.comparing(Number::doubleValue))));
    }
}


The two-argument union() uses a TreeSet that compares by the entries' toString(), which lets us store both Integer and Double values together in the Set. Alternatively, calling union() with a TreeSet supplier that compares by Number.doubleValue() gives us only 5 distinct elements:

[1, 1.0, 2, 2.0, 3, 3.0, 4, 4.0, 5, 5.0]
[1, 2, 3, 4, 5]


Rounding back to your question, I suppose HashSet() is a reasonably safe bet, since it doesn't do anything fanciful other than the usual hashCode()/equals() checks. And as you correctly inferred, since the method return type is just a Set, callers of union() should not have any other expectations - the Set interface simply says "this is how I am intended to be used.".