None of this needs to be taken on trust. Three commands will confirm any claim on this page against your own code:
javac -d out BindingDemo.java # compile as normaljavap -c -p out/BindingDemo.class # read the instructionsjava -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -cp out BindingDemo
The first prints bytecode with the comment column naming the exact field or method each instruction refers to. The second shows what the JIT decided at run time. Reading those two outputs side by side is the fastest way to internalise the whole topic, because you are looking at the real decisions rather than a diagram of them.
One caution when experimenting: a debugger that shows a value for a field may be reading the other field from the same object, since both slots exist. Trust the bytecode over what a variable inspector displays.
Frequently Asked Questions
What is the difference between static and dynamic binding in Java?
Static binding fixes the target of a call from the declared types while compiling, so the same target is used for every object. Dynamic binding picks the target from the actual object at run time, which is what makes an overridden method run. In bytecode the distinction shows up directly: static calls compile to invokestatic or getstatic with the target named in the instruction, while dynamic calls compile to invokevirtual or invokeinterface with only the name fixed.
Which methods and fields use static binding in Java?
Static methods, static field accesses, constructors, and explicit super calls are settled at compile time. Private and final instance methods are also effectively static because no subclass can replace them, even though they compile to invokevirtual like any other instance method. Instance field reads are static too, and are settled from the reference type rather than the object.
Is late binding slower than early binding?
Not in any way you should design around. A late-bound call is an invokevirtual instruction, not a reflective call, and modern JIT compilers profile the call site and inline the implementation behind a type guard, even when several receiver types have been observed. Marking a parameter final does not change the bytecode at all, so it cannot help. Profile before changing a design for performance.
Why do instance fields not dispatch on the actual object?
Because a field access compiles to getfield with the field named in the instruction, taken from the reference type. If a subclass declares a field with the same name, both fields exist as separate slots in the object and the compiler will not merge them. This is why fields are never polymorphic in Java and why reading a shadowed field through a supertype reference returns the parent's value.
Does a final method use a different bytecode instruction?
No. A final instance method compiles to invokevirtual, exactly like a non-final one. What final guarantees is that no subclass can replace it, which lets the JIT devirtualize the call without a guard. The instruction itself is identical, so the benefit is a stronger guarantee rather than a cheaper call site.
Do private methods still use invokespecial?
Not since Java 11. Older compilers emitted invokespecial for a call to a private method, and a great deal of online material still shows that. Since Java 11 they emit invokevirtual, because the change to private interface methods made that necessary. The behaviour is unchanged either way, since a private method cannot be inherited and so can never be overridden.
Are static final constants read from the field at run time?
Usually not at all. When a static final field is initialised with a compile-time constant, javac copies the value into every class that uses it and emits a literal or ldc rather than a getstatic. Compile-time string concatenation is folded the same way. This is why changing a public static final constant requires recompiling everything that reads it.
Is overloading static binding or dynamic binding?
Static. The compiler selects the overload from the declared types of the arguments before the program runs, and that choice is baked into the instruction. If no overload matches, it is a compile error rather than a decision deferred to run time. Overriding is the dynamic case.
When is real late binding actually needed?
When the method to call is not known until run time, which in practice means reflection. Frameworks use it to discover annotated methods, invoke handlers by name, and instantiate types from configuration. That is the genuine cost of late binding, and it is worth paying only when the alternative is recompiling code that already knows the name.