Data Stream as Disjoint intervals
Using a TreeSet effectively

Since you are already using a TreeSet as the underlying data structure, you need not check if it contains the incoming number val or not in addNum(int) as any Set implementation will not store duplicate values. Also, to skip the first element in the TreeSet, you can do:

int start = streamNums.first();
Iterator&lt;Integer&gt; streamIter = streamNums.tailSet(start, false).iterator();


This avoids having to next() on the Iterator manually.

Inverting ifs

public List&lt;Interval&gt; getIntervals() {
    ArrayList&lt;Interval&gt; buffer = new ArrayList&lt;Interval&gt;();
    if(!streamNums.isEmpty()) {
        // ...
    }
    return buffer;
}


You can reduce one level of nesting if you simply returned a new ArrayList first:

public List&lt;Interval&gt; getIntervals() {
    if(streamNums.isEmpty()) {
        return new ArrayList&lt;&gt;();
    }
    List&lt;Interval&gt; buffer = new ArrayList&lt;&gt;();
    // ...
    return buffer;
}


Interfaces over implementations and type inference

As illustrated above, buffer can be declared as a List instead of ArrayList, and at least you are not making the same mistake in the method return type declaration. Also, since Java 7, you can rely on type inference to eliminate mentioning the generic type a second time, by using the 'diamond operator' &lt;&gt;.

Java 8 stream-based processing

Since you are on Java 8, you can look towards its stream-based processing capabilities as the appropriate way of solving your problem. It's relatively simple to adapt your solution to reduce an integer stream to a List&lt;Interval&gt;, or more specifically to create a Collector out of your solution:

public class TreeInterval implements Collector&lt;Integer, TreeInterval, List&lt;Interval&gt;&gt; {

    private final TreeSet&lt;Integer&gt; cache = new TreeSet&lt;&gt;();

    @Override
    public BiConsumer&lt;TreeInterval, Integer&gt; accumulator() {
        return (ti, integer) -&gt; ti.cache.add(integer);
    }

    @Override
    public Set&lt;Characteristics&gt; characteristics() {
        return Collections.singleton(Characteristics.UNORDERED);
    }

    @Override
    public BinaryOperator&lt;TreeInterval&gt; combiner() {
        return (a, b) -&gt; { a.cache.addAll(b.cache); return a; };
    }

    private List&lt;Interval&gt; toResult() {
        if (cache.isEmpty()) {
            return new ArrayList&lt;&gt;();
        }
        List&lt;Interval&gt; buffer = new ArrayList&lt;&gt;();
        int start = cache.first();
        int end = start;
        Iterator&lt;Integer&gt; iterator = cache.tailSet(start, false).iterator();
        while (iterator.hasNext()) {
            int i = iterator.next();
            if (i == end + 1) {
                end = i;
            } else {
                buffer.add(new Interval(start, end));
                start = i;
                end = i;
            }
        }
        buffer.add(new Interval(start, end));
        return buffer;
    }

    @Override
    public Function&lt;TreeInterval, List&lt;Interval&gt;&gt; finisher() {
        return TreeInterval::toResult;
    }

    @Override
    public Supplier&lt;TreeInterval&gt; supplier() {
        return TreeInterval::new;
    }
}


In summary, a Collector needs to know how to:


supply an accumulator: represented by the supplier() method.
accumulate an Integer: represented by the accumulator() method.
combine the accumulators into one: represented by the combiner() method.
extract the results of the last accumulator to the required type: represented by the finisher() method. In turn, your solution is used as a method reference within this.
provide a set of characteristics that describes itself, for potential optimization: represented by the characteristics() method.


For illustration, this is how it can be used:

public static void main(String[] args) {
    IntStream.of(1, 3, 7, 2, 6)
        .boxed()
        .collect(new TreeInterval())
        .forEach(System.out::println);
}

// Output
[1,3]
[6,7]



Construct an IntStream with the values 1, 3, 7, 2, 6.
Box it into a Stream&lt;Integer&gt;.
Collect the values using an instance of TreeInterval.
For each Interval, print it using System.out.println(Object).