As we know, the Java language is an object-oriented programming language. In other words, the fundamental concept, i.e. the foundation of the foundation, is that everything is an object. Objects are described using classes. Classes, in turn, define state and behavior. For example, a bank account may have state in the form of the amount of money in the account and have behaviors that increase and decrease the account balance. In Java, behaviors are implemented through methods. Learning how to define methods comes at the very beginning of your Java studies. For example, in Oracle's official tutorial, under the heading "Defining Methods". There are two important things to take note of here:
  • Each method has a signature. The signature consists of the name of the method and its input parameters.
  • A return type must be specified for methods. You declare a method's return type in its method declaration.
The return type is not part of the method signature. Again, this is a consequence of the fact that Java is a strongly typed language and the compiler wants to know what types are used and where, in advance and as much as possible. Again, this is to save us from mistakes. Basically, it's all for a good cause. And it seems to me that this once again instills in us a culture of handling data. So, the type of the return value is specified for methods. And the return keyword in Java is used to actually do the returning. Java return Keyword - 1

What does return do in Java

The return keyword is a control flow statement, as described in the Oracle tutorial here. You can also read about how to return values in the "Returning a Value from a Method" section of the official tutorial. The compiler carefully keeps track of whether it can ensure that a method's return value matches the method's specified return type. Let's use Tutorialspoint's Online IDE to consider an example. Let's look at the primeval example:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
As we can see, the main method is executed here, which is the program's entry point. Lines of code are executed from top to bottom. Our main method cannot return a value. If we try to return a value there, we will get an error: "Error: Main method must return a value of type void". Accordingly, the method simply outputs to the screen. Now let's move the string literal into a separate method for generating the message:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println(getHelloMessage());
    }
    
    public static String getHelloMessage() {
        return "Hello World";
    }
    
}
As we can see, we used the return keyword to indicate the return value, which we then passed to the println method. The declaration of the getHelloMessage method indicates that the method will return a String. This allows the compiler to check that the method's actions are consistent with how it is declared. Naturally, the return type specified in a method declaration can be broader than the type of the value actually returned in the code, i.e. the important thing is that a type conversion is possible. Otherwise, we will get an error at compile time: "Error: incompatible types". By the way, you probably have a question: Why is return considered a control flow statement? Because it can disrupt the normal top-down flow of a program. Example:

public class HelloWorld {

    public static void main(String[] args) {
        if (args.length == 0) {
            return;
        }
        for (String arg : args) {
            System.out.println(arg);
        }
    }
    
}
As you can see from the example, we interrupt the main method in Java program if it is called without arguments. It is important to remember that if you have code after a return statement, it will not be accessible. Our smart compiler will notice it and prevent you from running such a program. For example, this code does not compile:

public static void main(String[] args) {
        System.out.println("1");
        return;
// we use output method after return statement, which is incorrect 
        System.out.println("2");
 }
There is one dirty hack to get around this. For example, for debugging purposes, or for some other reason. The code above can be fixed by wrapping the return statement in an if block:

if (2==2) {
    return;
}

Return statement during error handling

There is something else very tricky — we can use return in conjunction with error handling. I want to say right away that using a return statement in a catch block is very, very bad form, so you should avoid it. But we need an example, right? Here it is:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Value: " + getIntValue());
    }
    
    public static int getIntValue() {
        int value = 1;
        try {
            System.out.println("Something terrible happens");
            throw new Exception();
        } catch (Exception e) {
            System.out.println("Cached value: " + value);
            return value;
        } finally {
            value++;
            System.out.println("New value: " + value);
        }
    }
    
}
At first glance, it seems that 2 should be returned, since finally is always executed. But no, the return value will be 1, and the change to the variable in the finally block will be ignored. Moreover, suppose that value contains an object and we say value = null in the finally block. Then that object, not null, would be returned in the catch block. But a return statement would work correctly in the finally block. Obviously, your coworkers won't thank you for little surprises involving return statements.

void.class

And finally. There's this strange construct you can write: void.class. Hmm. Why and what does it mean? There really are various frameworks and tricky cases involving the Java Reflection API where this can be very useful. For example, you can check what type a method returns:

import java.lang.reflect.Method;

public class HelloWorld {

    public void getVoidValue() {
    }

    public static void main(String[] args) {
        for (Method method : HelloWorld.class.getDeclaredMethods()) {
            System.out.println(method.getReturnType() == void.class);
        }
    }
}
This can be useful in test frameworks where you need to replace the actual code in methods. But to do this, you need to understand how a method behaves (i.e. what type it returns). There is also a second way to implement the main method in the code above:

public static void main(String[] args) {
        for (Method method : HelloWorld.class.getDeclaredMethods()) {
            System.out.println(method.getReturnType() == Void.TYPE);
        }
 }
You can read a pretty interesting discussion of the difference between the two on Stack Overflow: What is the difference between java.lang.Void and void?