HomeGuidesJava
Java11 min readCode verified

Overloading, static, and Where Your Arguments Go

Four methods can share one name, and the compiler picks between them without ever looking at what they return. A method called straight from main can fail for a reason that sounds like nonsense until you know what static means. And the two words everyone swaps — parameter and argument — are a form and the thing written on it. Every listing here was compiled and run on OpenJDK 21.

A word you already overload#

You already know how to do this. You do it every day in English, without once being confused by it.

You open a door. You open a bottle. You open a bank account. You open a conversation. One word, four completely different actions, and nobody has ever had to stop and ask which one you meant.

How do you tell? By what comes after the word. “Open” plus a door is one thing; “open” plus an account is another. You never work it out from the result, because the result is not always there to look at.

Java does exactly that with method names, and it is called overloading. The rest of this page is that idea, plus the two words your notes keep swapping, plus the one keyword that decides whether a method needs an object at all.

Every listing below was compiled and run on OpenJDK 21, and every error message is the real one javac printed.

The blank line, and what you write on it#

Start with the smaller confusion, because the rest is easier once it is settled.

public class Greet {
    public static void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
    public static void main(String[] args) {
        greet("Sam");
        String who = "Ada";
        greet(who);
    }
}
Hello, Sam!
Hello, Ada!

Think of a paper form with a line on it: Name: ________. The printed blank is the parameter. What somebody writes on it is the argument.

TermWhere it livesIn the code above
parameterin the method’s own line, written onceString name
argumentat the call, supplied fresh every time"Sam", then who

Say the method line out loud as: “greet takes one String, and inside here I will call it name.” The blank does not care what it gets filled in with. It was "Sam" the first time and the contents of who the second, and greet did not need to know the difference.

One name, several methods#

Now the main event. Four methods, all called add, in one class:

public class Over {
    public static int add(int a, int b)          { return a + b; }
    public static double add(double a, double b) { return a + b; }
    public static int add(int a, int b, int c)   { return a + b + c; }
    public static String add(String a, String b) { return a + b; }

    public static void main(String[] args) {
        System.out.println(add(2, 3));
        System.out.println(add(2.5, 3.5));
        System.out.println(add(1, 2, 3));
        System.out.println(add("2", "3"));
    }
}
5
6.0
6
23

Four calls, four different methods, one name. The compiler chose each one by looking at how many arguments it was handed and what type each was. That list of types is the method’s signature — its name plus its parameter types, which together are what makes it distinguishable.

The last line is worth a second look. add("2", "3") prints 23, not 5, because + on two Strings joins them end to end rather than adding them. Nothing went wrong: that is the String version doing precisely what it says.

Widening still applies while the compiler is choosing:

public class Ambig {
    public static void show(int x)    { System.out.println("int"); }
    public static void show(double x) { System.out.println("double"); }
    public static void main(String[] args) {
        show(5);
        show(5.0);
        show('A');
    }
}
int
double
int

show('A') prints int. There is no show(char), so Java promotes the char to an int and takes the closest match it can reach. A char is a number wearing a costume, and the overload rules see straight through the costume.

Chosen by what you hand it, never by what it returns#

This is the part that gets examined, and the reason is worth having rather than memorizing.

public class BadOver {
    public static int    total(int a, int b) { return a + b; }
    public static double total(int a, int b) { return a + b; }

    public static void main(String[] args) {
        System.out.println(total(2, 3));
    }
}
BadOver.java:3: error: method total(int,int) is already defined in class BadOver
    public static double total(int a, int b) { return a + b; }
                         ^
1 error

Two methods, same name, same parameters, different return types. It does not compile, and the message does not even mention the return type — as far as the compiler is concerned these are simply the same method twice.

Why the rule has to exist

A method call is allowed to stand alone as a statement, with nothing catching what it returns. Write total(2, 3); on its own line and it is perfectly legal Java. At that moment there is no result being used, so there is nothing to choose by. The decision has to be makeable from what goes in, because what comes out is not always looked at.

Back to English: you can tell “open a door” from “open an account”. You cannot tell them apart by what you end up holding.

println has ten versions#

You have been calling an overloaded method since your first program.

import java.lang.reflect.Method;

public class Howmany {
    public static void main(String[] args) {
        int n = 0;
        for (Method m : java.io.PrintStream.class.getDeclaredMethods()) {
            if (m.getName().equals("println")) {
                n++;
                Class<?>[] p = m.getParameterTypes();
                System.out.println(p.length == 0 ? "println()"
                                 : "println(" + p[0].getSimpleName() + ")");
            }
        }
        System.out.println("total: " + n);
    }
}
println(String)
println(Object)
println(float)
println(char[])
println(double)
println()
println(boolean)
println(char)
println(int)
println(long)
total: 10

Ten. That number came from asking the class itself, not from a textbook. System.out.println is not one clever method that copes with anything — it is ten ordinary methods sharing a name, and the compiler picks one every time you call it.

You can watch it pick:

public class Chars {
    public static void main(String[] args) {
        char[] letters = {'h', 'i'};
        System.out.println(letters);
        System.out.println("say: " + letters);
    }
}
hi
say: [C@659e0bfd

The first call matches println(char[]), which is written to print the characters. The second does not: "say: " + letters is a String before println ever sees it, so println(String) runs and the array has already been turned into [C@ plus a hash code. Same variable, same method name, two results, and the only thing that changed was the type being handed over. The hex digits differ on every run; the [C@ is the part that matters.

Which methods need an object, and which do not#

Every household has its own fridge, and what is in yours is nobody else’s business. “How many wheels does a car have?” is a different kind of question — you do not need to go and find a particular car to answer it.

That is the whole of static. A method with no static belongs to one object and usually reads or changes that object’s own data. A static method belongs to the class and needs no object at all.

public class Counter {
    private int count = 0;

    public void bump() { count++; }
    public int getCount() { return count; }

    public static void main(String[] args) {
        bump();
    }
}
Counter.java:8: error: non-static method bump() cannot be referenced from a static context
        bump();
        ^
1 error

Read the message as the complaint it is: bump has to happen to some counter, and you have not told me which one. main is static, so it starts running before any object exists. There is no counter to bump yet.

Make one, and it works:

public class Counter2 {
    private int count = 0;

    public void bump() { count++; }
    public int getCount() { return count; }

    public static void main(String[] args) {
        Counter2 a = new Counter2();
        Counter2 b = new Counter2();
        a.bump();
        a.bump();
        b.bump();
        System.out.println("a: " + a.getCount());
        System.out.println("b: " + b.getCount());
        System.out.println("rounded: " + Math.round(2.6));
    }
}
a: 2
b: 1
rounded: 3

a and b keep separate counts, because count belongs to each object rather than to the class. Two fridges, two sets of contents.

The last line is the contrast. Math.round(2.6) needed no object — there is no new Math() anywhere, and there never is.

When static is the right choice#

The test is one question: does this method need anything from a particular object?

If the method…Then
reads or changes an instance fieldit must not be static
works only from its own parametersstatic is usually right
is mainalways static — it runs before any object exists

Math.round, Math.ceil, Math.pow and the rest are all static for the same reason: they work on the number you hand them and nothing else. Put anything you like in, and it does not change how rounding behaves. That is why utility methods in library code are nearly always static, and why you call them on the class name rather than on an object.

What a method cannot change#

One more thing that surprises people, and it follows from the form analogy.

public class Pass {
    public static void addTen(int number) {
        number = number + 10;
        System.out.println("inside the method: " + number);
    }
    public static void main(String[] args) {
        int score = 5;
        addTen(score);
        System.out.println("back in main:      " + score);
    }
}
inside the method: 15
back in main:      5

The method did change number. It just was not changing score. What got handed over was a copy of the value, so writing on the copy leaves the original where it was, exactly as filling in a photocopied form leaves the original blank.

If you want the caller to end up with the new value, hand it back and catch it: make addTen return number, and write score = addTen(score);.

One sentence to carry into the exam

A method is chosen by what you hand it, never by what it hands back. Number of arguments and their types decide the overload; the return type is not part of the decision and cannot be used to make one.

Where these go wrong#

  • Trying to overload on return type alone. int total(int, int) and double total(int, int) is already defined in class, not a subtle warning. Change the parameters or change the name.
  • Calling an instance method straight from main. non-static method cannot be referenced from a static context means you never made an object. Make one, or make the method static if it needs no object.
  • Making a method static to silence that error, when it reads a field. The compiler then tells you the field cannot be referenced from a static context either, which is the same complaint one step further in. The fix was an object all along.
  • Swapping parameter and argument in an answer. The blank is the parameter; what goes in it is the argument. Exams mark this.
  • Expecting a method to change a primitive you passed in. It receives a copy. Return the new value and assign it.
  • Assuming + means addition. add("2", "3") is 23. With a String on either side, + joins rather than adds.
  • Expecting println to print an array’s contents after concatenating it. println(letters) prints hi; println("say: " + letters) prints [C@ and a hash. Concatenation happens first, and it picks the other overload.

Test yourself in the free Kestrel Exams app

Topic-selectable practice — offline, no ads, no account.

Practice Java →

Frequently asked questions#

What is the difference between a parameter and an argument in Java?

A parameter is the blank in the method's own line: in greet(String name), name is the parameter. An argument is what you actually put in the blank when you call it: in greet("Sam"), the string "Sam" is the argument. Parameters are written once, when the method is declared. Arguments are supplied fresh at every call. The words are often swapped in conversation, but exams ask for them precisely.

What is method overloading in Java?

Method overloading means two or more methods in the same class share a name and are told apart by their parameter lists. add(int, int) and add(double, double) and add(int, int, int) can all coexist. The compiler picks one by looking at the number and the types of the arguments you pass. It is the same thing English does with the word open: you open a door and you open an account, and nobody is confused, because what follows the word settles it.

Can two methods differ only by return type?

No. If two methods have the same name and the same parameter list, changing only the return type is not enough, and the class will not compile. javac reports: method total(int,int) is already defined in class BadOver. The reason is that a call like total(2, 3) can appear as a statement on its own, where nothing uses the returned value, so there would be no way to tell which method was meant. The choice has to be made from what goes in, because what comes out is not always looked at.

Why do I get 'non-static method cannot be referenced from a static context'?

Because main is static and the method you called is not. A non-static method belongs to an individual object, so it needs one to work on, and main runs before any object exists. Either make an object first and call the method on it, as in Counter2 c = new Counter2(); c.bump();, or make the method static if it genuinely does not need any object's data.

When should a method be static?

When it does not need anything from a particular object. Math.round(2.6) is the standard example: it works on the number you hand it and nothing else, so there is no reason to build a Math object first. If a method reads or changes an instance field, it must not be static. If it only uses its own parameters, static is usually right, and this is why most library utility methods are static.

How many versions of println are there?

Ten in java.io.PrintStream: println(), and one each for boolean, char, char[], double, float, int, long, Object and String. That count comes from asking the class itself through reflection, not from memory. It is the reason System.out.println works with everything you throw at it: it is not one clever method, it is ten ordinary overloaded ones.

If I change a parameter inside a method, does the caller see it?

Not for a primitive. The method receives a copy of the value, so assigning to the parameter changes only that copy, and the caller's variable is untouched. A method that prints 15 inside itself can leave the caller's variable at 5. If you want the caller to have the new value, return it and assign it: score = addTen(score).

Suggest a change

Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.