DEV Community

Mohamed Akthar
Mohamed Akthar

Posted on

3 Spring Boot Mistakes I Made (And How to Fix Them)

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));
}
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

"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
}
Enter fullscreen mode Exit fullscreen mode

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)