📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
🧾 Introduction
Writing Java methods is easy —
but writing good methods that are short, clear, and powerful is an art.
Clean methods:
✅ Make your code easier to read
✅ Make your code easier to test
✅ Make your entire project easier to maintain
In this guide, you’ll learn how to write better Java methods with real-world examples and practical tips you can apply immediately.
✅ 1. Keep Methods Short (Prefer Under 15 Lines)
Problem:
Long methods are hard to understand and harder to debug.
Solution:
✅ Focus each method on a single, small task.
✅ If a method is growing too large, split it into smaller private helper methods.
❌ Before (Messy and Long Checkout Code)
public void checkoutOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems() == null || order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have at least one item");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be greater than zero");
}
paymentService.processPayment(order.getPaymentDetails());
inventoryService.updateStock(order.getItems());
shippingService.prepareShipment(order);
notificationService.sendOrderConfirmation(order.getUserEmail(), order.getOrderId());
}
- One method doing validation, payment, inventory, shipping, notification.
- Long method, mixing multiple domains.
✅ After (Correct Real-World Refactor)
public void checkoutOrder(Order order) {
validateOrder(order);
processPaymentAndInventory(order);
finalizeShippingAndNotify(order);
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems() == null || order.getItems().isEmpty()) {
throw new IllegalArgumentException("Order must have at least one item");
}
if (order.getTotalAmount() <= 0) {
throw new IllegalArgumentException("Order total must be greater than zero");
}
}
private void processPaymentAndInventory(Order order) {
paymentService.processPayment(order.getPaymentDetails());
inventoryService.updateStock(order.getItems());
}
private void finalizeShippingAndNotify(Order order) {
shippingService.prepareShipment(order);
notificationService.sendOrderConfirmation(order.getUserEmail(), order.getOrderId());
}
✅ Each method now does one simple thing.
✅ Easier to read, test, and reuse.
✅ 2. Name Methods Clearly (Verb + Noun)
Problem:
Unclear method names force readers to guess what the method does.
Solution:
✅ Name methods with action words.
✅ Method name = Verb + Noun (example: sendEmail()
, calculateSalary()
, updateStock()
).
Bad Examples:
public void email(User user) { }
public void stuff(List<Item> items) { }
Good Examples:
public void sendConfirmationEmail(User user) { }
public void updateInventoryStock(List<Item> items) { }
✅ Clear names save future readers from confusion — even if that reader is you!
✅ 3. Limit the Number of Method Parameters (Prefer ≤ 3)
Problem:
When a method takes too many parameters, it becomes:
- Harder to read
- Harder to understand
- Very easy to pass arguments in the wrong order
- Difficult to maintain when requirements change
Solution:
✅ Prefer at most 2–3 parameters per method.
✅ If more parameters are needed, group them into an object that represents a real-world concept.
✅ This keeps your code clean, safe, and scalable as the project grows.
❌ Bad Example (Too Many Parameters):
public void registerUser(String firstName,
String lastName,
String email,
String password,
int age,
String country,
String phoneNumber) {
// Registration logic
}
Problems:
- Hard to remember parameter order.
- Easy to accidentally swap email and phoneNumber.
- If requirements change (e.g., add
referralCode
), the method signature will keep growing.
✅ Good Example (Group into an Object):
1. Create a Request Object:
public class UserRegistrationRequest {
private String firstName;
private String lastName;
private String email;
private String password;
private int age;
private String country;
private String phoneNumber;
// Constructors, Getters, Setters
}
2. Update the Method:
public void registerUser(UserRegistrationRequest request) {
// Access request.getFirstName(), request.getEmail(), etc.
}
Benefits:
✅ Method stays clean and short — just one parameter.
✅ Adding new fields (e.g., referralCode
) won't break the method signature.
✅ Stronger typing — compiler helps ensure correct values.
✅ Easier to pass data between layers (Controller → Service → Repository).
“If you feel your method parameters growing beyond 3, it’s time to introduce a proper object that groups them meaningfully.”
✅ Your code stays future-proof and much easier to maintain.
✅ 4. Favor Returning Results Instead of Modifying Arguments
Problem:
Modifying objects passed as method arguments often leads to hidden side-effects.
✅ It’s unclear whether the object was changed inside the method — causing unexpected bugs.
✅ Instead, methods should return a new result or make the change explicit.
🔴 Bad Example (Modifies Argument — Hidden Side Effect)
public void capitalizeName(Customer customer) {
customer.setName(customer.getName().toUpperCase());
}
Why this is bad:
- The method silently changes the
customer
object. - If someone reads the code where
capitalizeName(customer)
is called,
they won't know thatcustomer.name
is now capitalized. - Hidden changes are dangerous in large systems.
✅ Good Example (Return a New Result)
public Customer capitalizeCustomerName(Customer customer) {
Customer updatedCustomer = new Customer();
updatedCustomer.setId(customer.getId());
updatedCustomer.setEmail(customer.getEmail());
updatedCustomer.setName(customer.getName().toUpperCase());
return updatedCustomer;
}
Usage:
Customer customer = customerService.findById(1L);
customer = capitalizeCustomerName(customer);
✅ Now:
- It’s explicit: we clearly assign the returned modified object.
- No silent modifications behind the scenes.
- Much easier to test, debug, and reason about.
Even Cleaner: Use a Constructor for Updated Objects
If your Customer
class has a proper constructor,
you can directly create a new instance:
public Customer capitalizeCustomerName(Customer customer) {
return new Customer(
customer.getId(),
customer.getEmail(),
customer.getName().toUpperCase()
);
}
✅ This style preserves immutability and clearly signals changes.
Clean Java methods make changes obvious, not hidden.
✅ Return new objects instead of modifying passed arguments —
especially for critical or shared data structures 🚀.
✅ 5. Use Meaningful Return Types (Optional, List, DTOs)
Problem:
Returning raw types like null
, empty String
, bare arrays
, or even Object
makes your code:
- Less expressive
- Harder to reason about
- Prone to
NullPointerException
Solution:
✅ Return types that clearly tell the story.
- Use
Optional<T>
when something might be missing. - Use
List<T>
,Set<T>
, orMap<K, V>
instead of arrays. - Use domain-specific DTOs (Data Transfer Objects) for rich responses.
- Avoid returning
null
— prefer meaningful defaults where appropriate.
📦 Example 1: Use Optional
Instead of Returning Null
❌ Bad Example:
public User findUserByEmail(String email) {
User user = database.findByEmail(email);
if (user != null) {
return user;
}
return null;
}
✅ Good Example:
public Optional<User> findUserByEmail(String email) {
return Optional.ofNullable(database.findByEmail(email));
}
✅ Clear contract: maybe user exists, maybe not. No guessing.
📦 Example 2: Return List<T>
Instead of Array
❌ Bad Example:
public User[] getAllUsers() {
return database.findAllUsers();
}
✅ Good Example:
public List<User> getAllUsers() {
return database.findAllUsers();
}
✅ List
is more flexible, easier to work with (streams, map/filter operations), and conveys intent clearly.
📦 Example 3: Return DTO Instead of Raw Entity
Suppose you have a heavy Order
entity with 30+ fields, but your frontend only needs 5 fields.
❌ Bad Example:
public Order getOrderDetails(Long orderId) {
return orderRepository.findById(orderId);
}
✅ Good Example:
public OrderSummaryDTO getOrderDetails(Long orderId) {
Order order = orderRepository.findById(orderId);
return new OrderSummaryDTO(order.getId(), order.getTotalAmount(), order.getStatus());
}
Where OrderSummaryDTO
looks like:
public class OrderSummaryDTO {
private Long id;
private double totalAmount;
private String status;
public OrderSummaryDTO(Long id, double totalAmount, String status) {
this.id = id;
this.totalAmount = totalAmount;
this.status = status;
}
// Getters
}
✅ You control exactly what you expose.
✅ Cleaner API contracts.
✅ Safer and more efficient.
📦 Example 4: Return Meaningful Defaults Instead of Null
❌ Bad Example:
public List<Product> getProductsByCategory(String category) {
List<Product> products = database.findByCategory(category);
return products == null ? null : products;
}
✅ Good Example:
public List<Product> getProductsByCategory(String category) {
List<Product> products = database.findByCategory(category);
return products != null ? products : Collections.emptyList();
}
✅ Always safe to iterate on the result — no more null checks needed.
🏆 Final Clean Code Tip:
“Your method’s return type should tell a clear story —
whether the result is optional, multiple items, or a structured DTO,
not leave people guessing or writing defensive null-check code.”
✅ Makes downstream code much safer, cleaner, and self-explanatory.
✅ 6. Handle Exceptions Inside Methods Properly
Problem:
Letting raw exceptions bubble up everywhere makes your code:
- Fragile
- Hard to debug
- Painful for users (ugly error messages)
- Unsafe for APIs and clients
Solution:
✅ Catch specific exceptions where recovery is possible.
✅ Otherwise, wrap and rethrow meaningful custom exceptions.
✅ Never leak technical exceptions (e.g., SQL errors, IO errors) directly to upper layers.
❌ Bad Example (Leaking Raw Exception)
public User findUserByEmail(String email) {
return database.findUser(email); // May throw SQLException directly!
}
Problem:
- If database fails, callers get random SQLExceptions.
- No clean error handling.
✅ Good Example (Catching and Wrapping Exception)
public User findUserByEmail(String email) {
try {
return userRepository.findByEmail(email);
} catch (DatabaseConnectionException ex) {
throw new ServiceException("Failed to retrieve user by email", ex);
}
}
Where:
public class ServiceException extends RuntimeException {
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
}
🎯 Why This Is Better:
✅ The outside world only sees ServiceException — consistent, meaningful.
✅ Logs or monitoring tools can still access the original cause internally.
✅ Your API stays clean, safe, and predictable.
🏆 Tip:
Catch only where you can handle meaningfully, else wrap and rethrow cleanly.
✅ 7. Document Behavior with JavaDoc (Where Needed)
Problem:
If you don’t document public methods properly:
- New developers waste time understanding the method.
- Behavior expectations become assumptions.
- Code reviews become slower and painful.
Solution:
✅ Write a short JavaDoc for public methods, especially when:
- Behavior isn’t completely obvious
- Parameters/returns have specific rules
- Exceptions might be thrown
- Special cases apply
❌ Bad Example (No Documentation)
public Order applyDiscount(Order order) {
if (order.getTotalAmount() > 1000) {
order.setDiscount(10);
}
return order;
}
New developers won’t know:
- What threshold is applied?
- What discount percentage is applied?
✅ Good Example (Clear JavaDoc)
/**
* Applies a 10% discount if the order total exceeds 1000 units.
*
* @param order the order to which the discount should be applied
* @return the updated order with discount applied, if applicable
* @throws IllegalArgumentException if the order is null
*/
public Order applyDiscount(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getTotalAmount() > 1000) {
order.setDiscount(10);
}
return order;
}
🎯 Why This Is Better:
✅ Any developer reading the method knows immediately:
- When discount applies
- What discount applies
- Possible exceptions thrown
✅ No hidden surprises.
✅ Smoother onboarding, fewer misunderstandings.
📌 Quick Recap: How to Write Better Java Methods

✅ Final Thoughts
✅ Writing short, clear, powerful methods is not just about clean code —
it’s about building better systems that scale and survive over time.
✅ Focus on clarity, single-responsibility, and small smart improvements.
✅ Your future self — and your team — will thank you for it.
Good developers write code that works.
Great developers write methods that are clean, simple, and powerful.
Comments
Post a Comment
Leave Comment