After 2+ years with Spring Boot, these are mistakes I still catch myself making.
1. Missing Input Validation
I've shipped endpoints like this way too many times:
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest req) {
// frontend handles validation anyway right?
return ResponseEntity.ok(authService.register(req));
}
Then someone sends an empty email. Or a 500 character username. Or just {}. Now there's garbage in the database and I'm spending my evening fixing it.
What I do now:
public class RegisterRequest {
@NotBlank
@Size(min = 3, max = 30)
private String username;
@Email
@NotBlank
private String email;
}
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest req) {
return ResponseEntity.ok(authService.register(req));
}
Takes minutes. Saves hours.
2. The N+1 Query
Had an endpoint to get transactions with their details:
@GetMapping("/transactions")
public List<TxnResponse> getAll() {
var txns = txnRepo.findByUserId(userId);
return txns.stream()
.map(t -> new TxnResponse(t.getId(), t.getAmount(), t.getDetails())) // oops
.toList();
}
t.getDetails() fires a query. For each transaction. 100 transactions = 101 queries. Endpoint took 4 seconds.
Fix:
@Query("SELECT t FROM Transaction t JOIN FETCH t.details WHERE t.userId = :userId")
List<Transaction> findByUserIdWithDetails(@Param("userId") Long userId);
Down to 1 query.
Enable spring.jpa.show-sql=true while developing. If you see the same select repeating, you've got N+1.
3. Catching Exception
public AccountDTO getAccount(Long id) {
try {
var acc = accountRepo.findById(id).orElseThrow();
return mapper.toDto(acc);
} catch (Exception e) {
log.error("failed to get account", e);
throw new RuntimeException("something went wrong");
}
}
"something went wrong" - super helpful when debugging at 11pm.
Was it a db timeout? A null somewhere? A bug I introduced? The log just says "failed to get account" for everything.
What I do now:
public AccountDTO getAccount(Long id) {
try {
var acc = accountRepo.findById(id)
.orElseThrow(() -> new NotFoundException("account not found: " + id));
return mapper.toDto(acc);
} catch (DataAccessException e) {
log.error("db error fetching account {}", id, e);
throw new ServiceException("database unavailable");
}
// let other exceptions bubble up - they're probably bugs
}
Specific exceptions = specific fixes.
tldr
- Add
@Valid- don't trust input - Check for N+1 - turn on sql logging
- Catch specific exceptions - "something went wrong" helps nobody or even better use @ControllerAdvice if possible
Basic stuff but easy to skip when you're rushing.
What mistakes do you keep making? Comment down below
Top comments (0)