Defining Methods
Declare methods with the right return type, name and parameters, and split request handling into small named steps you can test.
modifiers 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 inreturnor athrow. The compiler reportsmissing return statementotherwise. A switch expression with adefaultis a tidy way to cover every case. - Doing two jobs. A method called
validateAndSaveis two methods. Keeping validation separate lets you reuse it for updates and test it without a database. - Hidden side effects. A method named
calculateTotalthat 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
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.
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 1A 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.
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
// 100Returning 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.
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