Overload vs override

Polymorphism is the essence of OOP. Overloading and overriding are two tools for achieving polymorphic behavior in Java.

Overloading implements ad-hoc polymorphism. This means that the "same" method can work with different parameters. Technically, these are simply two different methods with the same name but different parameter sets. It's important to remember that for overloading, differences in modifiers, return types, and exception lists alone are not sufficient.

Ad-hoc polymorphism isn't truly polymorphic because it involves early binding (aka static binding, or static dispatch). This means that the choice of the specific method variant is based on the type of the variable, not the object it references, and this determination is made at compile time.

If a class declares two overloaded methods and the argument in a call matches both, a compilation error occurs. For example, in the code below, the compiler cannot choose between the println method variants with char[] and String parameters because null can match both.
Overload vs override
Overriding implements subtype polymorphism. It involves the implementation or replacement of a method from a non-final superclass or interface. This mechanism allows behavior where an instance is stored under the type of its parent, but the method implementation specific to the actual subtype is used. For example:
List<String> list = new LinkedList<>();
list.add("foo");


Here, the add method called is common to all lists, but the element is added specifically to the linked list.

The specific method choice is made at the last moment, during program execution, based on the object's type. This is known as late binding or dynamic dispatch.

Overriding is directly related to the Liskov Substitution Principle (LSP): in well-designed object-oriented code, an overridden method should not be distinguishable from the original for the calling code.

It's customary to annotate overridden methods with @Override. This annotation is optional, however if a method marked with @Override does not actually override anything, compilation will fail.

When overriding, you can narrow the range of thrown exceptions or the return type, and you can replace the access modifier with a less restrictive one.

Static methods cannot be overridden, only overloaded.

You can read more about the inner workings of method binding in this article.