Java: How is Optional.empty () compilation used?

This seems like a really stupid question, but I can't figure out why this usage Optional<T>


import java.util.Optional;

public class Driver {
    static void foo(Optional<String> x) { }

    public static void main() {



defined as returning to me Optional<T>

. Internally, the Driver::main

expression Optional.empty()

looks like it will return Optional<Object>

as I am not parameterizing usage Optional

, so I expect it to return to Object

as a type parameter. Then I pass the Optional<Object>

function awaiting Optional<String>

, which is a descending parameter that shouldn't be allowed. I would expect to see something like:

incompatible types: Optional<Object> cannot be converted to Optional<String>


However, the code compiles just fine. Clearly my thought process is wrong here ... but where?

Let me clarify what I'm looking for in the answer here ... I know what an output type is. I don't understand how this is happening here and what has changed in Java 7 language to Java 8. For example, this bit of code compiles fine in Java 8, but does not work in Java 7:

final class Opt<T> {
    private final T value;

    Opt(T x) {
        value = x;

    public static <T> Opt<T> empty() {
        return new Opt<T>(null);

public class Driver {
    static void bar(Opt<String> x) { }

    public static void main() {


How does this work in Java 8 when you have to deal with things like overloading? Is there a specific section of the Java language spec that talks about this?


source to share

4 answers

This is because of the way the empty () method is defined in Optional:

public static<T> Optional<T> empty() {
    Optional<T> t = (Optional<T>) EMPTY;
    return t;


Notice the type parameter of the method above:

public static<T> Optional<T> empty() {
             ^^^ method type parameter


This means that when you call empty () it will bind T to the context of its caller, in your case String. For more information, see the Target Types section of this page in the Java Tutorial:



Since this part of your question has not been resolved, I will try to summarize what changed between Java 7 and Java 8.

Java 7 already had type inference, eg. You can write

List<String> list=Collections.emptyList();



List<String> getList() {
    return Collections.emptyList();


But this type of inference was pretty limited, for example. what didn't work (apart from others):

List<String> list=Collections.unmodifiableList(Collections.emptyList());



List<String> getList() {
    return condition? new ArrayList<>(): Collections.emptyList();


These two examples now work in Java 8. This new feature is called object-type inference because it now uses the target type to find matching type arguments. Besides the fact that nested calls and nested method conditions work as in the above examples, it also fixes the following example:

List<Number> numbers=Arrays.asList(1, 2, 3, 4);


As said, Java 7 also has type inference, but in this example, it will infer List<Integer>

as a result the type of the expression from the arguments passed to asList

and hence generate an error.

Unlike Java 8 has target type deduction and will use target type deduction List<Number>

as expression type and find out that the whole statement is valid as you can use objects Integer

where Number


Note that Optional.empty()

both Collections.emptyList()

use the same type of general design. I used the latter in my examples as it already exists in Java 7.



This is because the compiler does type inference.

When the compiler reads:

static void foo(Optional<String> x) { }

public static void main() {


  • It knows what it Optional.<T>empty()

    takes T

    as a parameter.
  • He knows what to foo


  • This means that T



This was introduced when generics were introduced, and its mechanism may have been improved with Java 8 javac


Note that this is compiler dependent: ECJ (Eclipse JDT Compiler) does not understand the same java source that Javac does (they compile the "same" bytecode, however).



Java 8 added a type interface, which means it will work out the type of the expression based on how it is used.

An explicit example:

Object o = () -> System.out.println("Hello World"); 


does not compile because it does not know the type of the expression, however

Runnable r = () -> System.out.println("Hello World"); 

Object o = (Runnable) () -> System.out.println("Hello World"); 


compile fine. those. the type of expression changes due to how it is used.



All Articles