Defining Methods

Declare methods with the right return type, name and parameters, and split request handling into small named steps you can test.

Syntaxmodifiers ReturnType methodName(Type param1, Type param2) { // body return value; // omitted when ReturnType is void }

A method is a named block of code that takes inputs, does one job and optionally hands back a result. Every method in Java lives inside a type. In a compact source file the methods you write next to main belong to a class the compiler creates for you; in a normal project they sit inside a class you declare.

The parts of a declaration

private static int totalPence(int quantity, int unitPence) {
    return quantity * unitPence;
}

Reading left to right: optional modifiers (private, static), the return type (int, or void for nothing), the name, the parameter list with a type for every parameter, and the body. The name plus the parameter types form the method's signature; that is what the compiler uses to tell methods apart.

Names are lower camelCase verbs or verb phrases: validate, findById, toResponse. Methods that answer a yes/no question usually start with is, has or can.

Why split code into methods

A request handler that parses input, validates it, applies business rules and builds a response in one 80-line block is hard to read and impossible to test in pieces. Pull each step into its own method and the handler becomes a short list of calls that reads like a description of the endpoint. Each step can then be tested with plain inputs, without starting a server.

A good rule: if you feel the need to write a comment above a chunk of code explaining what it does, that chunk probably wants to be a method whose name says the same thing.

Mistakes people make

  • Not returning on every path. If the return type is not void, every path through the body must end in return or a throw. The compiler reports missing return statement otherwise. A switch expression with a default is a tidy way to cover every case.
  • Doing two jobs. A method called validateAndSave is two methods. Keeping validation separate lets you reuse it for updates and test it without a database.
  • Hidden side effects. A method named calculateTotal that also writes to the database surprises the next reader. Let names tell the truth about what happens.
  • Long parameter lists. Five or more parameters, especially several of the same type, invite mixed-up arguments. Group them into a record such as OrderRequest.

In Spring and other frameworks, the controller methods you write are ordinary methods; the framework simply calls them for you. The habits from this lesson carry straight over.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Split a POST /orders handler into validate, price and save methods that can each be unit tested.
  • Turn article titles into URL slugs in one reusable method shared by the create and update endpoints.
  • Clamp a client-supplied page size in a helper before it reaches the database query.

More examples

Extracting the steps of a handler

The handler now reads as validate, then calculate, then respond, and each step can be tested with a plain record. Grouping the inputs into a record keeps the parameter lists short.

Example · java
record OrderRequest(String email, int quantity) {}

String handle(OrderRequest req) {
    Optional<String> error = validate(req);
    if (error.isPresent()) {
        return "400 " + error.get();
    }
    return "201 total=" + totalPence(req.quantity(), 1250);
}

Optional<String> validate(OrderRequest req) {
    if (req.email() == null || !req.email().contains("@")) {
        return Optional.of("email is invalid");
    }
    if (req.quantity() < 1) {
        return Optional.of("quantity must be at least 1");
    }
    return Optional.empty();
}

int totalPence(int quantity, int unitPence) {
    return quantity * unitPence;
}

void main() {
    IO.println(handle(new OrderRequest("[email protected]", 3)));
    IO.println(handle(new OrderRequest("not-an-email", 3)));
    IO.println(handle(new OrderRequest("[email protected]", 0)));
}
// Output:
// 201 total=3750
// 400 email is invalid
// 400 quantity must be at least 1

A private static helper in a classic class

Clamping a client's page size stops one request asking the database for a million rows. Marking the helper private keeps it out of the class's public surface.

Example · java
public class Main {
    private static final int DEFAULT_PAGE_SIZE = 20;
    private static final int MAX_PAGE_SIZE = 100;

    // Only this class needs it, so it stays private.
    private static int clampPageSize(int requested) {
        if (requested < 1) {
            return DEFAULT_PAGE_SIZE;
        }
        return Math.min(requested, MAX_PAGE_SIZE);
    }

    public static void main(String[] args) {
        System.out.println(clampPageSize(0));
        System.out.println(clampPageSize(50));
        System.out.println(clampPageSize(5000));
    }
}
// Output:
// 20
// 50
// 100

Returning on every path with a switch expression

A switch expression must produce a value for every input, so with a default branch the compiler knows the method always returns. An if/else chain missing its final else would fail with missing return statement.

Example · java
String statusClass(int code) {
    return switch (code / 100) {
        case 2 -> "success";
        case 3 -> "redirect";
        case 4 -> "client error";
        case 5 -> "server error";
        default -> "unknown";
    };
}

void main() {
    for (int code : new int[] {200, 304, 404, 503, 99}) {
        IO.println(code + " " + statusClass(code));
    }
}
// Output:
// 200 success
// 304 redirect
// 404 client error
// 503 server error
// 99 unknown

Discussion

  • Be the first to comment on this lesson.