Skip to content
Ameen Abozaid
PublishedBEG-014-L03

Method signatures and overloading

  • Lesson 3 of 8
  • 11 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

You already use overloading without noticing. System.out.println(5), System.out.println("hi") and System.out.println(2.5) look like the same method, and in a sense they are: one name, one idea ("print this"), with a different version for each kind of input. Java lets you write your own families like this. It is called overloading.

In the recipe-card picture, think of a card called "Make sauce": one version for tomato, one for cream. Same name for the same kind of job, with different ingredients. When you ask for "sauce", the cook looks at what you handed over and picks the matching card.

This lesson explains the signature (what makes a method unique), how to overload, how Java chooses between versions, and the situations where overloading fails to compile. It builds on Lesson 2 (parameters and types) and Module 9 (widening conversions).

Learning Objectives

By the end of this lesson you will be able to:

  • State what a method signature is and what is not part of it.
  • Overload a method by changing the number or types of its parameters.
  • Predict which overloaded version a given call selects.
  • Explain why two methods that differ only in return type, or only in parameter names, do not compile.
  • Recognise an ambiguous call and fix it.
  • Decide when overloading is a good design choice.

Written Explanation

The signature

The Oracle tutorial defines a method's signature as its name plus the types of its parameters, in order. For example:

DeclarationSignature
static int add(int a, int b)add(int, int)
static double add(double a, double b)add(double, double)
static void show(String s)show(String)

What is not part of the signature: the return type, the parameter names, and modifiers like static. Two methods in one class cannot have the same signature. The tutorial states this plainly: the compiler does not use the return type to tell methods apart.

Overloading

Overloading means declaring several methods in the same class with the same name but different parameter lists. The lists must differ in the number of parameters, their types, or the order of different types.

java
static int max(int a, int b)       { ... }
static double max(double a, double b) { ... }

Java treats these as two separate methods that happen to share a name. Use it when the action is conceptually the same and only the input differs: max of two ints, max of two doubles; area of a square (one side) or a rectangle (two sides). Do not use it for different jobs (print and delete should not share a name just because the parameters differ). The Oracle tutorial adds a warning that overloading should be used sparingly, because too many versions make code hard to read.

How Java picks one

At each call, the compiler chooses the version, before the program runs. It looks at the number and types of the arguments, and picks the best match. The full rules in the Java Language Specification have several phases; the beginner-level summary is:

  1. Keep only the versions with the right number of parameters.
  2. Prefer a version whose parameter types match the arguments exactly or by a widening conversion (int to long, int to double, char to int), with no boxing.
  3. Among several that fit, choose the most specific one: the one whose parameter types are closest. For an int argument, show(int) beats show(long) and show(double), because int can widen to either of those but they cannot narrow back.
  4. If no version fits, or two fit equally well, the call does not compile.

In Example 3 you will see the output of a call chosen this way, from a real run. (Boxing, the conversion that turns int into Integer, is outside this course module. The specification says the compiler first tries versions that need no boxing. I describe that rule from the specification's overview and did not read the full chapter text, so treat the detail as background.)

Failing to compile

Three situations to know, with the compiler's real messages.

Same signature, different return type. The return type cannot save you:

java
static int f(int a)    { return a; }
static double f(int b) { return b; }
text
error: method f(int) is already defined in class Err

The parameter names (a and b) do not count either.

An ambiguous call. Two versions fit equally well:

java
static void g(int a, double b) { }
static void g(double a, int b) { }
...
g(1, 2);
text
error: reference to g is ambiguous
  both method g(int,double) in Err and method g(double,int) in Err match

Each version needs one widening, so neither is more specific. The fix is to make the call say what you mean, for example g(1, 2.0), or to rename a method.

A null argument. h(null) with h(String) and h(Integer) is also ambiguous, since null fits both. You will not need to write this yet, but remember that overloads and null do not mix.

Overloading versus changing the value

An overloaded call is chosen by the declared types of the arguments, not by what the value "looks like". Passing the int variable n to a method with an int and a double version always calls the int one. One small surprise: because char widens to int, max('a', 'b') selects max(int, int) and returns 98, the numeric codes of the letters. It matches the rule but is rarely what you wanted.

Examples

Example 1: two versions of max (simple)

java
public class Max {
    static int max(int a, int b) {
        System.out.println("int version");
        return a > b ? a : b;
    }

    static double max(double a, double b) {
        System.out.println("double version");
        return a > b ? a : b;
    }

    public static void main(String[] args) {
        System.out.println(max(2, 5));
        System.out.println(max(2.5, 1.0));
        System.out.println(max(2, 1.5));
    }
}

Output (JDK 21):

text
int version
5
double version
2.5
double version
2.0

(The println calls inside max are only there so you can see the choice; by Lesson 2's rule a real max would not print.) The first call has two int arguments and matches the int version. The second has doubles. The third mixes an int and a double: only the double version fits, because the 2 widens to 2.0 while 1.5 cannot become an int. The result 2.0 shows that widening happened.

Example 2: area for different shapes (practical)

java
public class Area2 {
    static double area(double side) {
        return side * side;
    }

    static double area(double width, double height) {
        return width * height;
    }

    static double area(double base, double height, boolean triangle) {
        return triangle ? base * height / 2 : base * height;
    }

    public static void main(String[] args) {
        System.out.println(area(3));
        System.out.println(area(3, 4));
        System.out.println(area(3, 4, true));
    }
}

Output:

text
9.0
12.0
6.0

The number of arguments chooses the version: one for a square, two for a rectangle, three for the optional triangle form. This is a legitimate use because all three answer the same question, "what is the area?". A flag argument such as true is a bit unclear to read, though; separate methods named triangleArea and rectangleArea are often a better design. Choose names for clarity first.

Example 3: defaults through overloads, and the type-based pick (realistic)

A common pattern is to let a short version call a longer one, filling in default values.

java
public class Price {
    static double total(double unitPrice, int quantity, double taxRate) {
        return unitPrice * quantity * (1 + taxRate);
    }

    static double total(double unitPrice, int quantity) {
        return total(unitPrice, quantity, 0.25);
    }

    static double total(double unitPrice) {
        return total(unitPrice, 1);
    }

    static void show(int n)    { System.out.println("int " + n); }
    static void show(long n)   { System.out.println("long " + n); }
    static void show(double n) { System.out.println("double " + n); }
    static void show(String s) { System.out.println("String " + s); }

    public static void main(String[] args) {
        System.out.println(total(20));
        System.out.println(total(20, 3));
        System.out.println(total(20, 3, 0.0));
        show(7);
        show(7L);
        show(7.0f);
        show("seven");
        byte b = 7;
        show(b);
    }
}

Output:

text
25.0
75.0
60.0
int 7
long 7
double 7.0
String seven
int 7

The three total versions share one real calculation, so a fix there fixes all three: no duplicated logic. The show calls demonstrate the pick: 7 is an int, 7L a long, 7.0f (a float) widens to double because there is no float version, "seven" is a String, and the byte variable b widens to the closest available type, int. The default tax of 0.25 is again only a round number for the example.

Backend Insight

Overloading is everywhere in the Java libraries you will use. String.valueOf has versions for int, double, boolean and more; Math.max has int, long, float and double versions; and database or logging APIs often offer a short call (log(message)) and a longer one (log(message, level)). As a backend developer you mostly call overloads, so knowing how Java picks one helps you read documentation and understand a surprising result such as 97 + 1 printing a number instead of a letter.

When you write your own service methods, the "short version calls the full version" pattern from Example 3 keeps defaults in one place. The risk is the same as in any API: a clutter of near-identical overloads confuses callers, and in a public API you cannot easily remove one later without breaking other people's code.

Common Mistakes

Mistake 1: Overloading by return type only

Changing only int to double in front of the name does not create a new method. You get method ... is already defined. Change the parameter list, or give the methods different names.

Mistake 2: Thinking parameter names distinguish methods

f(int a) and f(int b) have the same signature. Only the types (and their count and order) count.

Mistake 3: Ambiguous calls

If two versions each need a different widening, the call is ambiguous. Make the arguments exact (g(1, 2.0)), cast one, or rename the methods.

Mistake 4: Expecting the value, not the type, to decide

Java picks by the declared type of the argument, decided at compile time. max('a', 'b') uses the int version and gives 98. Be explicit if you want something else.

Mistake 5: Overloading unrelated jobs

Sharing a name for different actions (process(int) meaning save, process(String) meaning send) forces readers to guess. Overload only when the meaning is the same.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Write the signature of static String greet(String name, int times).

Show solution A1

A1. greet(String, int).

A2. Can a class contain static int f(int x) and static int f(int y)? Explain.

Show solution A2

A2. No. The parameter names do not matter, so both have the signature f(int). The compiler reports that the method is already defined.

A3. Given static void show(int n) and static void show(String s), which version does show("hi") call?

Show solution A3

A3. The String version, because the argument type is String.

Level B: Application

B1. Write overloaded square(int) returning int and square(double) returning double. Say what square(3) and square(1.5) return.

Show solution B1

B1.

java
static int square(int n) { return n * n; }
static double square(double n) { return n * n; }

square(3) calls the int version and returns 9; square(1.5) calls the double version and returns 2.25.

B2. With static void p(int a), static void p(long a) and static void p(double a), say which runs for p(5), p(5L) and p(5.0). Then say which runs if the long version is deleted and you call p(5L).

Show solution B2

B2. p(5) runs p(int), p(5L) runs p(long) and p(5.0) runs p(double). With the long version deleted, p(5L) runs p(double), because a long can widen to double but not to int.

B3. Will static int count(String s) and static long count(String t) compile together? Why or why not?

Show solution B3

B3. No. The signatures are both count(String); the return type and the parameter name are not part of the signature, so the compiler reports a duplicate definition.

Level C: Challenge

C1. Write three overloads of describe: one for an int (returns "number 5" style text), one for a String, and one for an int and a String together. Show one call of each.

Show solution C1

C1.

java
static String describe(int n) { return "number " + n; }
static String describe(String s) { return "text " + s; }
static String describe(int n, String s) { return s + " x" + n; }

// calls
describe(5);           // "number 5"
describe("tea");       // "text tea"
describe(2, "tea");    // "tea x2"

C2. Build static double discountedPrice(double price) that applies a 10 percent discount, and an overload discountedPrice(double price, double percent). Make the one-parameter version call the two-parameter one. Explain why this is better than copying the formula into both.

Show solution C2

C2.

java
static double discountedPrice(double price, double percent) {
    return price * (1 - percent / 100);
}

static double discountedPrice(double price) {
    return discountedPrice(price, 10);
}

With the formula in one place, a correction (for example rounding rules) is made once. Two copies can drift apart and give different results for what looks like the same rule.

Mini Challenge

Write a small Format program with overloaded label methods: label(String text) returns "[text]", label(String text, int width) returns the same text padded with spaces on the right up to width characters (use a loop, or return as is if the text is already that long), and label(int number) returns "[#number]". Have the one-argument String version reuse the others if you can. Call each once and print the results.

Check your work: three distinct signatures, each call selects the version you expect, and no formula or bracket logic is duplicated.

Summary & Next Lesson

Key takeaways

  • A signature is the method name plus its parameter types in order. Return type and parameter names are not part of it.
  • Overloading is the same name with different parameter lists, used when the action is the same and only the input differs.
  • The compiler picks the version at compile time: matching count, then exact or widening type match, then the most specific. If two fit equally, the call is ambiguous and does not compile.
  • Changing only the return type is not overloading and does not compile.
  • Let short overloads call a full version to keep logic in one place.

Next lesson

You can now name methods, give them inputs and outputs, and group them by name. The next lesson, "Scope: where variables live", answers a question you have already met in practice: which variables can each part of the code see, and what happens to a parameter when the method changes it?

Self-check

  1. What is a method signature?
  2. Can two methods differ only in return type?
  3. Which version does max(3, 4) call if both max(int, int) and max(double, double) exist?
  4. When is a call to an overloaded method ambiguous?
  5. Give one good use of overloading.
Show answers
  1. The method name plus the types of its parameters, in order.
  2. No; the compiler reports that the method is already defined.
  3. max(int, int), the exact match.
  4. When two versions fit equally well, for example each needing a different widening, so neither is more specific.
  5. The same action on different inputs, such as max for ints and for doubles, or a short version that fills in defaults and calls a longer one.

References

  • Oracle, Defining Methods. Verified: the signature is the name plus parameter types; overloading by number or type of arguments; the compiler does not consider return type; the advice to use overloading sparingly.
  • Oracle, The Java Language Specification, SE 21, Chapter 15 (Expressions). The page loaded but the body of section 15.12.2 (choosing a method) lay beyond the text I could read, so the three-step summary of how a method is chosen comes from my reading of the specification's overview plus the behaviour of the compiler in the runs above, not from a quoted passage.
  • All outputs and error messages come from compiling and running the examples with JDK 21 (javac 21.0.10).

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.