Writing Clean and Readable Java Code — Best Practices
Writing code that works is just the beginning. Writing code that others (and future you) can read and maintain is what separates beginners from professionals.
Why Clean Code Matters
- You read code 10× more than you write it
- Most bugs come from misunderstanding existing code
- Clean code is easier to debug, test, and extend
- Teams move faster with readable codebases
Naming Conventions
Java Standard Naming
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | BankAccount, HttpClient |
| Methods | camelCase | calculateTotal(), getUserName() |
| Variables | camelCase | firstName, totalAmount |
| Constants | UPPER_SNAKE | MAX_SIZE, DEFAULT_TIMEOUT |
| Packages | lowercase | com.example.utils |
Good Names Are Self-Documenting
// Bad
int d; // elapsed time in days
int s; // student count
List<int[]> list1;
// Good
int elapsedTimeInDays;
int studentCount;
List<int[]> flaggedCells;
Boolean Naming
Use is, has, can, should prefixes:
boolean isActive;
boolean hasPermission;
boolean canEdit;
boolean shouldRetry;
Method Naming
Use verbs that describe the action:
// Bad
data();
process();
handle();
// Good
fetchUserData();
calculateShippingCost();
validateEmailAddress();
Formatting and Style
Consistent Indentation
Use 4 spaces (Java standard). Never mix tabs and spaces.
Braces on the Same Line
// Java convention (K&R style)
if (condition) {
doSomething();
} else {
doOther();
}
One Statement Per Line
// Bad
int a = 1; int b = 2; int c = 3;
// Good
int a = 1;
int b = 2;
int c = 3;
Blank Lines for Readability
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
public Order findById(long id) {
return repository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
public void cancel(Order order) {
order.setStatus(Status.CANCELLED);
repository.save(order);
}
}
Method Design
Keep Methods Short
A method should do one thing and fit on one screen (~20 lines max).
// Bad — does too many things
public void processOrder(Order order) {
// validate (10 lines)
// calculate totals (15 lines)
// apply discounts (10 lines)
// save to database (5 lines)
// send confirmation email (10 lines)
}
// Good — each step is a method
public void processOrder(Order order) {
validate(order);
calculateTotals(order);
applyDiscounts(order);
save(order);
sendConfirmation(order);
}
Limit Parameters
// Bad — too many parameters
public void createUser(String name, String email, String phone,
String address, String city, String country) { }
// Good — use an object
public void createUser(UserRequest request) { }
Return Early
// Bad — deep nesting
public double calculateDiscount(Customer c) {
if (c != null) {
if (c.isPremium()) {
if (c.getOrders() > 10) {
return 0.20;
} else {
return 0.10;
}
} else {
return 0.05;
}
}
return 0;
}
// Good — guard clauses
public double calculateDiscount(Customer c) {
if (c == null) return 0;
if (!c.isPremium()) return 0.05;
if (c.getOrders() > 10) return 0.20;
return 0.10;
}
Avoid Magic Numbers
// Bad
if (age > 17) { }
Thread.sleep(86400000);
// Good
final int LEGAL_AGE = 18;
if (age >= LEGAL_AGE) { }
final long ONE_DAY_MS = 24 * 60 * 60 * 1000L;
Thread.sleep(ONE_DAY_MS);
DRY — Don’t Repeat Yourself
// Bad — duplicated validation
public void saveUser(User user) {
if (user.getName() == null || user.getName().isBlank()) {
throw new ValidationException("Name required");
}
// save...
}
public void updateUser(User user) {
if (user.getName() == null || user.getName().isBlank()) {
throw new ValidationException("Name required");
}
// update...
}
// Good — extract common logic
private void validateUser(User user) {
if (user.getName() == null || user.getName().isBlank()) {
throw new ValidationException("Name required");
}
}
Code Smells to Avoid
| Smell | Problem | Fix |
|---|---|---|
| Long methods | Hard to understand | Extract smaller methods |
| Deep nesting | Hard to follow | Guard clauses, extract methods |
| Magic numbers | Unclear meaning | Named constants |
| Commented-out code | Clutter | Delete it (use Git) |
| God class | Does everything | Split into focused classes |
| Duplicate code | Maintenance nightmare | Extract shared method/class |
Comments: When and How
// BAD — states the obvious
i++; // increment i
// GOOD — explains WHY
// Retry 3 times because the external API has intermittent failures
for (int attempt = 0; attempt < 3; attempt++) { ... }
// BEST — code explains itself, no comment needed
final int MAX_API_RETRIES = 3;
for (int attempt = 0; attempt < MAX_API_RETRIES; attempt++) { ... }
Use Modern Java Features
// Old
String result;
if (value != null) {
result = value;
} else {
result = "default";
}
// Modern
String result = Objects.requireNonNullElse(value, "default");
// Old
List<String> filtered = new ArrayList<>();
for (String s : list) {
if (s.startsWith("A")) {
filtered.add(s.toUpperCase());
}
}
// Modern
List<String> filtered = list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.toList();
Quick Checklist
Before committing code, ask yourself:
- Are my names descriptive?
- Are methods under 20 lines?
- Is there duplicated code I can extract?
- Are there magic numbers?
- Can I reduce nesting?
- Would a new reader understand this?
Summary
| Principle | Action |
|---|---|
| Meaningful names | Self-documenting code |
| Short methods | One responsibility each |
| Guard clauses | Reduce nesting |
| Named constants | No magic numbers |
| DRY | Extract common code |
| Modern features | Use streams, records, var |
What’s Next?
The final chapter of Core Java Basics — learn how to find and fix bugs effectively: Debugging in Java.