Java Return Values
A return value is the result a method sends back to the code that called it. Returning a value, rather than printing it or storing it in a shared variable, makes a method a reusable building block: the caller decides what to do with the result.
The return Statement
static int square(int n) {
return n * n;
}
int a = square(4); // 16
int b = square(3) + square(4); // 25
System.out.println(square(square(2))); // 16
The declared return type (int) states what kind of value the method produces. return n * n; evaluates the expression and hands the result back. The call square(4) is itself an expression, so it can be assigned, combined with other expressions, or passed directly to another method.
The returned value must be compatible with the declared type:
static int half(int n) {
return n / 2.0; // compile error: double cannot be converted to int
}
Widening is automatic: a method declared to return double may return 5;.
void Methods
A method that produces no result declares the return type void:
static void logMessage(String msg) {
System.out.println("[LOG] " + msg);
}
It cannot be used as a value: String s = logMessage("x"); does not compile. A void method may use a bare return; to finish early:
static void sendReminder(String email) {
if (email == null || email.isBlank()) {
return; // nothing to do
}
System.out.println("Sending reminder to " + email);
}
return Ends the Method Immediately
Any statements after a return on the same path do not run. The compiler rejects code that can never be reached:
static int sign(int n) {
return n > 0 ? 1 : -1;
System.out.println("done"); // compile error: unreachable statement
}
Several return statements in one method are normal. Each returns from a different path:
static String classify(int age) {
if (age < 0) {
return "invalid";
}
if (age < 13) {
return "child";
}
if (age < 20) {
return "teen";
}
return "adult";
}
Because each return exits, no else is needed: reaching the second if already implies age >= 0. This early-return style keeps methods flat.
Every Path Must Return
For a non-void method, the compiler verifies that every possible path ends in a return (or a throw):
static String grade(int score) {
if (score >= 50) {
return "pass";
} else if (score < 50) {
return "fail";
}
} // compile error: missing return statement
To a human the two conditions cover everything, but the compiler does not reason about numeric ranges; it sees an if/else if with no final else. Use a plain else, or a final return after the chain.
A throw also satisfies the rule, which is how methods handle impossible or invalid cases:
static double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}
Return Instead of Print
Compare two versions of the same calculation:
static void printAverage(int[] values) {
int sum = 0;
for (int v : values) sum += v;
System.out.println((double) sum / values.length);
}
static double average(int[] values) {
int sum = 0;
for (int v : values) sum += v;
return (double) sum / values.length;
}
printAverage can only print. average can be printed, formatted, compared, stored, written to a file, sent in an HTTP response, or checked in a unit test:
double avg = average(scores);
if (avg < 40) sendWarning();
System.out.printf("Average: %.1f%n", avg);
Separating computation from output is one of the most useful habits in program design.
Returning Objects and Arrays
A method can return any type, including arrays, collections, and your own classes:
static int[] minMax(int[] values) {
int min = values[0], max = values[0];
for (int v : values) {
if (v < min) min = v;
if (v > max) max = v;
}
return new int[]{min, max};
}
int[] r = minMax(new int[]{7, 2, 9, 4});
System.out.println(r[0] + " to " + r[1]); // 2 to 9
This works, but the caller must remember that index 0 is the minimum. A record makes the result self-describing:
record Range(int min, int max) {}
static Range minMax(int[] values) {
int min = values[0], max = values[0];
for (int v : values) {
min = Math.min(min, v);
max = Math.max(max, v);
}
return new Range(min, max);
}
Range r = minMax(new int[]{7, 2, 9, 4});
System.out.println(r.min() + " to " + r.max()); // 2 to 9
Java methods return exactly one value. When several values belong together, return an object that groups them.
Returning "No Result"
Methods that search for something must decide what to return when nothing is found.
A sentinel value is a special value that cannot be a real result, such as -1 for an index:
int idx = "hello".indexOf('z'); // -1
This is fine when an obvious impossible value exists.
null is common for objects but pushes the burden onto the caller, who must remember to check:
User u = findUser("ada");
System.out.println(u.getName()); // NullPointerException if not found
Optional makes the possibility of absence part of the return type:
import java.util.Optional;
static Optional<String> findEmail(String username) {
if (username.equals("ada")) {
return Optional.of("ada@example.com");
}
return Optional.empty();
}
String email = findEmail("bob").orElse("no email on file");
findEmail("ada").ifPresent(e -> System.out.println("Mail to " + e));
The caller cannot treat the result as a plain String without deciding what happens when it is empty. Optional is intended for return values; it has its own lesson.
For collections, return an empty collection rather than null. List.of() or Collections.emptyList() lets callers loop over the result without a null check.
Returning a Reference to Internal State
Returning a mutable internal object lets callers modify it behind the class's back:
public class Course {
private final List<String> students = new ArrayList<>();
public List<String> getStudents() {
return students; // caller can add or remove students
}
public List<String> getStudentsSafe() {
return List.copyOf(students); // read-only snapshot
}
}
course.getStudents().clear(); // empties the course's real list
course.getStudentsSafe().clear(); // UnsupportedOperationException
Return an unmodifiable view or copy when callers should only read the data. This is part of encapsulation, covered in the OOP section.
Practical Example: Password Strength Checker
public class PasswordCheck {
record Result(int score, String verdict) {}
static boolean hasDigit(String s) {
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) return true;
}
return false;
}
static boolean hasUpper(String s) {
for (char c : s.toCharArray()) {
if (Character.isUpperCase(c)) return true;
}
return false;
}
static boolean hasSymbol(String s) {
for (char c : s.toCharArray()) {
if (!Character.isLetterOrDigit(c)) return true;
}
return false;
}
static Result evaluate(String password) {
if (password == null || password.length() < 8) {
return new Result(0, "Too short");
}
int score = 1;
if (hasDigit(password)) score++;
if (hasUpper(password)) score++;
if (hasSymbol(password)) score++;
if (password.length() >= 12) score++;
String verdict = switch (score) {
case 1, 2 -> "Weak";
case 3 -> "Fair";
case 4 -> "Strong";
default -> "Very strong";
};
return new Result(score, verdict);
}
public static void main(String[] args) {
String[] tests = {"abc", "password", "Password1", "Pass word#2026"};
for (String p : tests) {
Result r = evaluate(p);
System.out.printf("%-15s %d %s%n", p, r.score(), r.verdict());
}
}
}
Output:
abc 0 Too short
password 1 Weak
Password1 3 Fair
Pass word#2026 5 Very strong
The has... helpers return true the moment they find a match, so they stop scanning early, and return false only after checking every character. evaluate returns early for a password that is too short, then returns a Result record carrying both the numeric score and the label. Because nothing is printed inside these methods, the same checker could feed a web form's validation message or a unit test unchanged.