Return Values, void, and Composition
Pick a return type, hand a value back, let the caller decide what to do with it
In a nutshell
In the last lesson, values flowed into the method through parameters. Now you learn how a value flows out: through the method’s return type. Every method either:
- Returns a value of some declared type (
int,double,boolean,String, etc.). Inside the body, the keywordreturnships a value back to the caller and ends the method. - Or is declared
voidand returns nothing. Its job is the side effect (printing, modifying something, drawing).
Pick the return type before you write the body. Decide whether the caller should be able to use the result. If yes, return it. If the work is just a side effect, write void.
Today in three sentences. Methods that return are reusable. Methods that print are stuck.
voidis for side effects, not for hiding a missing return type.
After this lesson, you will be able to:
- Choose between
int,double,boolean,String, andvoidfor a given problem statement, and write the matching method header. - Use
returnto both ship a value back and exit the method. - Explain why a non-
voidmethod must produce a value on every code path, and why ignoring a returned value is almost always a bug. - Compose methods so that one method’s return value flows directly into another method’s argument list.
From CSCD 110. Python lets you write
def f(x): print(x)and call it as if it returns something — you just getNone. Java is stricter. If you call avoidmethod on the right side of an assignment, the compiler refuses. That strictness catches the “I forgot to return” bug at compile time instead of letting you find it at 2 a.m. with aNoneTypetraceback.
Picking the return type
Before you start writing the body of a method, ask one question: what does this method give back to its caller? The answer is the return type.
| Question the method answers | Return type |
|---|---|
| A whole number count? | int |
| A measurement with a fractional part? | double |
| Yes or no? | boolean |
| A piece of text? | String |
| A single character? | char |
| Nothing — just do this side effect | void |
Worked examples.
“Convert pounds to kilograms.” A measurement, fractional. Return type: double.
public static double poundsToKg(double pounds) {
return pounds * 0.453592;
}
“Is this score a passing grade?” Yes or no. Return type: boolean.
public static boolean isPassing(int score) {
return score >= 60;
}
“Translate this score into a letter grade.” A piece of text. Return type: String.
public static String letterGrade(int score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
“Print a banner with this title.” Nothing to give back, just a side effect. Return type: void.
public static void printBanner(String title) {
System.out.println("=== " + title + " ===");
}
A useful trick: write the header first, including the return type, and only then start filling in the body. The header forces you to commit to what the method does, which usually clarifies the body.
Common pitfall: defaulting to
voidbecause the body prints. If the body prints, ask whether the caller might also want to use the value (store it, compare it, pass it on). If so, return the value and let the caller decide whether to print. A method that returns is usable everywhere a method that prints is, and in many places where the printing version is not.
Check your understanding. What return type should each method have?
A. A method
daysInMonththat takes a month number and returns how many days are in it. B. A methodstudentGreetingthat takes a name and prints “Hi, <name>!” on the screen. C. A methodbmithat takes weight in kg and height in m and returns a body-mass-index value. D. A methodisVowelthat takes acharand answers yes-or-no.Reveal answer
A.
int(a whole-number count). B.void(its job is the side effect of printing). C.double(a fractional measurement). D.boolean(yes or no).
return: a value AND an exit
The keyword return does two things at the same time. It evaluates the expression that follows it, ships that value back to the caller, and immediately exits the method. Any code below the return is unreachable.
public static int absoluteValue(int n) {
if (n < 0) return -n; // exits the method right here
return n; // only runs if n >= 0
}
Two consequences worth memorizing.
(1) A non-void method must return a value on every code path. The compiler checks this. If there is a way to walk through the method body without hitting a return, the compiler refuses to compile, with the error missing return statement.
// Will not compile.
public static String letterGrade(int score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
// What if score is 50? The method falls off the end with no return.
// Compiler error: missing return statement.
}
The fix is to make sure every path returns. Either add a final unconditional return, or restructure with if/else if/else:
public static String letterGrade(int score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
return "F"; // covers everything < 70
}
(2) Ignoring the returned value of a non-void method silently throws away the work. The compiler does not warn about this. It is one of the easiest bugs to write and one of the hardest to spot in your own code.
public static int square(int n) {
return n * n;
}
square(5); // compiles fine. Computes 25. Throws 25 away. Useless.
To use the value, store it, print it, pass it on, or compare it. If you find yourself writing a method call as a stand-alone statement and the method’s return type is not void, double-check that you meant to.
Common pitfall: print-instead-of-return. A method whose only job is to print can be called only for that effect. A method that returns can be called and printed (
System.out.println(letterGrade(85))), stored (String g = letterGrade(85)), compared (if (letterGrade(85).equals("B")) ...), or passed on (writeToFile(letterGrade(85))). Default to returning. Letmain(or whichever method is in charge) handle the output.
Check your understanding. Which version is more reusable, and why?
// Version A public static void describe(int score) { if (score >= 60) System.out.println("Passing"); else System.out.println("Failing"); } // Version B public static String describe(int score) { if (score >= 60) return "Passing"; else return "Failing"; }Reveal answer
Version B is more reusable. Anywhere you want to print, you can print B (
System.out.println(describe(85))). But you can also store B’s result, compare it, write it to a file, or pass it on. Version A is locked intoSystem.out.println. If a future caller wanted to log the result to a file or check it inside anif, they would have to rewrite Version A. The general rule: do the work in the method, do the output in the caller.
void methods: do something, return nothing
A method declared with return type void does not produce a value. Its purpose is the side effect: a print, a write, a draw, a state change. You cannot use a void call on the right side of an assignment, and you cannot use it inside a larger expression.
public static void printRule(int width) {
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println();
}
printRule(20); // OK. Side effect happens.
String s = printRule(20); // Compiler error: void cannot be assigned.
System.out.println(printRule(20)); // Compiler error: void in expression.
Inside a void method, you have two options.
(a) Run to the closing brace. The method exits naturally. Most void methods are written this way.
(b) Use return; for an early exit. A bare return (no value) is legal in a void method and means “stop here, do not run the rest.”
public static void greet(String name) {
if (name == null || name.isEmpty()) {
System.out.println("(no name given)");
return; // bail out early
}
System.out.println("Hello, " + name + "!");
}
What you cannot do is return value; in a void method. That is a compile error: cannot return a value from method whose result type is void.
public static void shout(String word) {
return word.toUpperCase(); // Compiler error.
}
If a void method wants to give a value back, it should not be void. Change the return type to whatever the value is.
Common pitfall: putting
returnat the end of everyvoidmethod out of habit. Functions in some languages (and the way some students were taught) end withreturn. In Java, a barereturn;at the very end of avoidmethod is legal but redundant: the method was about to exit anyway. Savereturn;for genuine early-exit cases.
Check your understanding. Which of these are legal
voidmethod bodies?A.
{ System.out.println("hi"); }B.{ if (x < 0) return; System.out.println(x); }C.{ return 0; }D.{ return; return; }Reveal answer
A and B are legal. C is a compile error:
voidmethods cannot return a value. D compiles, but the secondreturn;is unreachable code (the compiler will issue a warning or an error depending on the IDE settings); after the firstreturn;the method has already exited.
Composition: methods calling methods
Once you have several methods, the call site is itself an expression. You can use the value returned by one method directly as the argument to another. This is composition, and it is the move that lets main shrink down to a few readable lines.
public static double readPositive(Scanner kb) {
double x = kb.nextDouble();
while (x <= 0) {
System.out.println("Please enter a positive number.");
x = kb.nextDouble();
}
return x;
}
public static double circleArea(double radius) {
return Math.PI * radius * radius;
}
public static void main(String[] args) {
Scanner kb = new Scanner(System.in);
System.out.println("Area = " + circleArea(readPositive(kb)));
}
Read the last line of main. The argument to circleArea is the return value of readPositive(kb). The argument to println is the string "Area = " concatenated with the return value of circleArea(...). Three method calls, one statement, no temporary variables.
The trace order matches the three-step protocol from lesson 5b. Java evaluates arguments left to right, fully, before any method body runs:
- Evaluate
readPositive(kb). The body ofreadPositiveruns to completion and produces, say,5.0. - The expression now reads
circleArea(5.0). Evaluate that. The body ofcircleArearuns and produces78.539.... - The expression now reads
"Area = " + 78.539.... Evaluate the concatenation, then callprintlnwith the resulting string.
Composition keeps main flat. Each method does one thing. Each call site reads like a sentence: “Print the area of a circle whose radius the user enters.” The structure of that sentence is the structure of your code.
Common pitfall: nesting too aggressively. Composition reads beautifully when each step has a clear name. It reads horribly when six expressions get jammed onto one line. If a single statement has three or more nested method calls and any of them are not obvious from their name, break it up with named local variables. Readability beats compactness.
Check your understanding. Given the same
circleAreaand a methodsquare(int n)that returnsn * n, what does this print?System.out.println(square(3) + square(4));Reveal answer
25. Java evaluates the arguments to+left to right:square(3)returns9, thensquare(4)returns16. The expression becomes9 + 16 = 25.printlnprints25. (Note: this is not the Pythagorean theorem because there is no square root. It is just9 + 16.)
Check your understanding. Why does this not compile?
public static void greet(String name) { System.out.println("Hi, " + name); } public static void main(String[] args) { System.out.println(greet("Alice")); }Reveal answer
greetis declaredvoid, so the callgreet("Alice")does not produce a value. ButSystem.out.println(...)requires some value as its argument (typically aString,int,double, etc.). Trying to put avoidcall where a value is required is a compile error:'void' type not allowed here. Either letgreetprint on its own (greet("Alice");as a stand-alone statement) or change its return type toStringand letmainprint the returned string.
Wrap up and what’s next
Recap.
- Pick the return type before you write the body. The return type announces what the method gives back.
returncarries a value back AND ends the method. Non-voidmethods mustreturnon every code path.- Ignoring the return value silently throws away the result. The compiler does not warn. Always store, print, compare, or pass on the value.
voidis for side effects.return value;invoidis a compile error.return;(early exit, no value) is legal.- Composition: a method’s return value can flow straight into another method’s argument list.
mainshrinks. Each method does one thing.
What you can do now. Read a problem statement and write the matching method header without drafting the body. Decide whether a method should be void or return something. Read a chain of method calls and trace it using the three-step protocol from lesson 5b.
Next up: Overloading, Scope, and Decomposition. Two methods can share a name as long as their parameter lists differ. Variables declared inside a method live and die with the call. And the design move at the heart of Week 5: take a verbose main and decompose it into 3 to 5 named methods that each do one thing.
Related resources
- Reges & Stepp, Building Java Programs, Chapter 3 sections 3.3 and 3.4 cover return values and
voidwith a Reges-style decomposition example. - FAQ entries on
missing return statementand the print-vs-return choice.