Method Declaration & Definition
A method's declaration (its signature) is the contract callers rely on; its definition is the body that fulfills that contract — understanding the split clarifies overloading, overriding, and interfaces alike.
In Java, a method's declaration describes what the method looks like from the outside — its name, parameters, and return type — while its definition (the body) describes how it actually works. Nearly every ordinary method you write bundles both together in one place, but the distinction becomes essential once interfaces, abstract methods, overloading, and overriding enter the picture.
Declaration vs Definition: The Distinction
The declaration (also called the method header) is everything up to the body: the access modifier, optional modifiers, return type, name, and parameter list. The definition is the { } body that supplies the actual implementation. For a normal (concrete) method, both appear together.
An abstract method or an interface method without a default body has a declaration but no definition at all — that's exactly what makes it abstract: it specifies the contract without specifying the implementation.
public int add(int a, int b) { // <-- declaration (header)
return a + b; // <-- definition (body)
}Method Signature
A method's signature is its name plus the number, order, and types of its parameters — the part the compiler uses to distinguish one method from another with the same name (overloading) and to match a call site to the right method. The return type and access modifier are NOT part of the signature.
void print(int x) // signature: print(int)
void print(int x, int y) // signature: print(int, int) - different signature, valid overload
void print(double x) // signature: print(double) - different signature, valid overload
// int print(int x) - would NOT be a valid overload alongside void print(int x):
// same signature (print(int)), different return type only -> compile errorAccess Modifier
The access modifier (public, protected, package-private/no modifier, or private) controls where a method can be called from. It's part of the declaration but does not affect the signature used for overload resolution.
public class Account {
public double getBalance() { return balance; } // callable from anywhere
private void logTransaction(String s) { /* ... */ } // callable only within Account
protected void applyInterest() { /* ... */ } // callable within package/subclasses
double internalHelper() { return 0; } // package-private (no modifier)
private double balance;
}Return Type in the Declaration
The declared return type constrains what the method's body is allowed to return, and constrains what callers can do with the result. Every return valueExpression; inside the body must produce something assignable to that declared type.
public String describe(int n) {
if (n == 0) {
return "zero"; // String literal - fine, matches return type
}
return String.valueOf(n); // also produces a String - fine
// return 42; would be a compile error: int is not a String
}Parameters in the Declaration
Each parameter in the declaration specifies a type and a name. The names are meaningful only within the method's own body — callers don't reference parameter names, only argument order and type (Java has no named-argument call syntax).
public void transfer(double amount, String toAccount) {
// amount and toAccount are only meaningful inside this method's body
}
transfer(250.00, "ACC-9981"); // caller supplies values positionally, not by nameMethod Body: Where the Definition Lives
The body is a block that implements the behavior the declaration promises. It can contain any valid statements: local variable declarations, control flow, calls to other methods, and — for non-void methods — return statements that supply the actual result.
public int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result; // body fulfills the declared contract: given an int, produce an int
}Declaration Without Definition: Abstract Methods
An abstract method has a declaration (ending in a semicolon, no body at all) but no definition — it's a contract that a concrete subclass must fulfill by providing an actual body. This is the clearest place the declaration/definition distinction becomes visible in real code.
abstract class Shape {
abstract double area(); // declaration only - no body, ends with ';'
}
class Circle extends Shape {
private double radius;
Circle(double radius) { this.radius = radius; }
@Override
double area() { // definition - Circle supplies the implementation
return Math.PI * radius * radius;
}
}Declaration Without Definition: Interface Methods
An ordinary interface method (no body) is likewise just a declaration — a contract every implementing class must define. Interfaces can also include default and static methods that do carry a definition, blurring the pure declaration/definition split for those specific cases.
interface Greeter {
String greet(String name); // declaration only - implementing classes define it
default String greetLoudly(String name) { // has a definition: this IS the body
return greet(name).toUpperCase();
}
}Implementation: Fulfilling a Declaration
When a class implements an interface method or overrides an abstract/parent method, its own method's signature and return type must be compatible with the declaration it's fulfilling (return-type covariance is allowed; see Method Overriding for the exact rules) — this is the compiler checking that the definition actually satisfies the original contract.
class EnglishGreeter implements Greeter {
@Override
public String greet(String name) { // must match Greeter's declared signature
return "Hello, " + name;
}
}Practical Usage: Why the Distinction Matters
Separating declaration from definition is what makes interfaces, abstract classes, and polymorphism work at all: code that calls a method through an interface or supertype reference only needs to know the declaration — the actual definition that runs is decided at runtime based on the real object's class. It also underlies overload resolution, which is based purely on the declared signature, not on what the body happens to do.
Greeter g = new EnglishGreeter(); // caller only relies on the DECLARATION: greet(String)
System.out.println(g.greet("Priya")); // the DEFINITION that runs depends on the actual objectEdge Cases
A method can be declared but deliberately left with an empty body ({ }) rather than no body at all — this is a valid definition (it does nothing), which is different from an abstract method's total absence of a body. It's a common no-op placeholder while a class is under development.
Constructors are technically not methods (they have no return type, not even void), but they follow a similar declaration/body structure and the same signature-matching logic drives constructor overloading.
void onEvent(String type) {
// intentionally empty for now - this IS a definition, just a trivial one
}Complete Example
Checkout's code only depends on PaymentProcessor's declaration of process(double); CreditCardProcessor supplies the actual definition that gets executed.
interface PaymentProcessor {
boolean process(double amount); // declaration only
}
class CreditCardProcessor implements PaymentProcessor {
@Override
public boolean process(double amount) { // definition fulfilling the interface's contract
if (amount <= 0) {
return false;
}
System.out.println("Charging $" + amount + " to credit card");
return true;
}
}
public class Checkout {
public static void main(String[] args) {
PaymentProcessor processor = new CreditCardProcessor();
System.out.println(processor.process(49.99)); // true
}
}Mental Model
Think of a method declaration as a job posting (title, required inputs, promised output) and a method definition as the person actually hired to do the work. Callers only need to read the job posting to know how to interact with the role — who's actually doing the work, and how, can vary (and in polymorphism, does vary) without the caller needing to change anything.
Quick Reference
Declaration (header): accessModifier returnType name(paramTypes)
Definition (body): { statements... }
Signature = name + parameter types (order matters) - NOT return type, NOT access modifier
Declaration WITHOUT a definition:
- abstract methods abstract double area();
- ordinary interface methods String greet(String name);
Declaration WITH a definition:
- ordinary concrete methods
- interface default/static methodsKey Takeaways
A method's declaration (header) is its externally visible contract; its definition (body) is the actual implementation.
A method's signature is its name plus parameter types and order — the return type and access modifier are not part of it.
Abstract methods and ordinary interface methods have a declaration but no definition; subclasses/implementers must supply one.
Interface default and static methods do carry a definition, unlike ordinary abstract interface methods.
Separating declaration from definition is what allows polymorphism: code can depend on a declaration while the definition that actually runs varies by object type at runtime.
abstract class Shape { abstract double area() { return 0; // error: abstract methods cannot have a body } } abstract class Shape { abstract double area(); // declaration only, no body }
An abstract method must not have a body at all — providing one is a compile error, since 'abstract' specifically means the definition is deferred to a subclass.
// intended as an overload, but only the return type differs void compute(int x) { } int compute(int x) { return x; } // compile error: duplicate signature void compute(int x) { } int computeAndReturn(int x) { return x; } // different name, or vary the parameter list
Overloading requires a different signature (name + parameter types/order); changing only the return type is not enough to distinguish two methods, so this is a compile error, not a valid overload.
interface Repository { void save(Object item); // declaration } class MemoryRepository implements Repository { // forgot to implement save(Object) - compile error } class MemoryRepository implements Repository { @Override public void save(Object item) { // actual definition } }
A concrete class implementing an interface must supply a definition for every abstract method the interface declares; leaving one unimplemented is a compile error unless the class itself is also declared abstract.
void process(int amount) { // empty body left by mistake, but caller expects real processing to happen } void process(int amount) { validate(amount); applyProcessing(amount); log(amount); }
An empty body is valid Java and compiles fine, but silently doing nothing when real behavior is expected is a logic bug, not a syntax issue — it's worth flagging with a TODO comment at minimum if left incomplete intentionally.
What two parts make up a typical concrete method, and what does each one specify?
The declaration (header), specifying the access modifier, return type, name, and parameters; and the definition (body), specifying the actual statements that implement the behavior.
What exactly is a method's signature, and what is explicitly NOT part of it?
The signature is the method's name plus the number, order, and types of its parameters. The return type and the access modifier are not part of the signature — two methods with the same name and parameter types but different return types are not valid overloads.
Give two situations where a method has a declaration but no definition.
An abstract method (abstract double area();) and an ordinary interface method without a default body (String greet(String name);) — both specify a contract without an implementation, which some other class must supply.
Do interface default methods have a definition, or only a declaration?
They have a definition — a default method includes an actual body in the interface itself, unlike an ordinary abstract interface method, so implementing classes are not required to override it.
Why does the distinction between declaration and definition matter for polymorphism?
Code that calls a method through an interface or supertype reference only needs to know the declaration to compile; which definition actually executes is resolved at runtime based on the real object's class, which is exactly what makes polymorphic dispatch possible.
Is an empty method body ({ }) the same as having no body at all (as in an abstract method)?
No — an empty body is still a valid definition (it just does nothing when called) and compiles fine for a concrete method; an abstract method having no body at all is a completely different, deliberate absence of an implementation that a subclass must supply.
Why can't void compute(int x) and int compute(int x) coexist in the same class?
They have the identical signature (compute(int)) — overload resolution and method identity are based on the signature, not the return type, so declaring both is a compile-time 'duplicate method' error.
Write an interface Validator with a declaration-only method boolean isValid(String s), then a class LengthValidator implementing it that returns true when s.length() > 3.
interface Validator { boolean isValid(String s); } class LengthValidator implements Validator { @Override public boolean isValid(String s) { return s.length() > 3; } }
Validator's isValid method is a declaration with no body, forming a contract; LengthValidator supplies the definition that actually implements it, satisfying the interface's requirement.
Does this compile, and why is BaseShape allowed to not implement area()?
Yes, it compiles. An abstract class is allowed to leave inherited abstract methods (including those required by an implemented interface) undefined, deferring the obligation to whichever concrete subclass eventually extends it — here, Square supplies the actual definition, satisfying the contract for the whole hierarchy.
Write two overloaded methods named describe: one taking an int and returning a String, one taking a double and returning a String, demonstrating that parameter type (not return type) drives overload resolution.
String describe(int n) { return "int: " + n; } String describe(double d) { return "double: " + d; }
Both methods share the name describe and the same return type, but their signatures differ by parameter type (int vs double), which is exactly what makes them distinct, valid overloads — the compiler picks the right one based on the argument's type at each call site.
What does this print, and how does makeNoise use a method it never defines itself?
It prints "Woof". Animal declares sound() as abstract (no definition) but defines makeNoise(), which calls sound() through the declaration alone; at runtime, the actual object is a Dog, so Dog's definition of sound() is the one that executes — this is polymorphism relying entirely on the declaration/definition split.
interface Shape {
double area();
}
abstract class BaseShape implements Shape {
// does not implement area()
}
class Square extends BaseShape {
double side = 4;
@Override
public double area() { return side * side; }
}abstract class Animal {
abstract String sound();
void makeNoise() {
System.out.println(sound());
}
}
class Dog extends Animal {
@Override
String sound() { return "Woof"; }
}
new Dog().makeNoise();