DEV Community

Ashish Saran Shakya
Ashish Saran Shakya

Posted on

🛠️ 7 Mistakes I Made Writing My First Spring Boot APIs (And What I Do Differently Now)

Spring Boot makes building APIs feel deceptively easy.

When I started out, I focused on just “getting things working.” But when things break—or just scale badly—you realize that working code isn’t always good code.

Here are 7 mistakes I made writing my first Spring Boot APIs, and what I now do to write cleaner, more maintainable backends.


1. Not Using Validation Annotations (@Valid, @NotNull, etc.)

What I did:

Manually validated request payloads deep inside controllers or services. Missed fields caused confusing errors or null pointer exceptions.

if (userRequest.getEmail() == null) {
    throw new IllegalArgumentException("Email is required");
}
Enter fullscreen mode Exit fullscreen mode

Why it's a problem:

  • Makes code noisy and repetitive
  • Delays validation too deep into the flow
  • Prone to human error

What I do now:

Use Spring's built-in validation support with annotations and @Valid.

public class UserRequest {
    @NotNull
    @Email
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

And in the controller:

@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserRequest request) {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Pair this with @ControllerAdvice to handle validation exceptions cleanly.


2. Hardcoding config with @Value instead of using @ConfigurationProperties

What I did:

Injected config values everywhere using @Value.

@Value("${aws.s3.bucket}")
private String bucketName;
Enter fullscreen mode Exit fullscreen mode

Why it's a problem:

  • Scattered config makes things hard to manage
  • No way to validate values
  • Harder to write unit tests

What I do now:

Use @ConfigurationProperties to bind entire config blocks into structured classes.

@ConfigurationProperties(prefix = "aws.s3")
public class S3Properties {
    private String bucket;
    // getters/setters
}
Enter fullscreen mode Exit fullscreen mode

This gives you validation, IDE support, and cleaner wiring.


3. Logging the Wrong Way

What I did:

Used System.out.println() and vague messages like “Error occurred” or “Debug here”.

Why it's a problem:

  • Not visible in production logs
  • Lacks context (who, what, when)
  • No severity levels or structured format

What I do now:

Use SLF4J with clear, parameterized logging:

log.info("User {} uploaded file {}", userId, fileName);
Enter fullscreen mode Exit fullscreen mode

And for errors:

log.error("Failed to process request for user {}", userId, e);
Enter fullscreen mode Exit fullscreen mode

Be intentional with log levels: info, warn, error, and avoid logging entire stack traces unless necessary.


4. Messy Exception Handling

What I did:

Wrapped everything in try-catch blocks, often rethrowing or printing stack traces manually.

try {
    ...
} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
Enter fullscreen mode Exit fullscreen mode

Why it's a problem:

  • Duplicates logic
  • Makes error responses inconsistent
  • Hides real issues in production

What I do now:

Define custom exceptions and handle them centrally using @ControllerAdvice.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String msg) {
        super(msg);
    }
}
Enter fullscreen mode Exit fullscreen mode

And in a global exception handler:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> handleNotFound(ResourceNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                             .body(new ErrorResponse(ex.getMessage()));
    }
}
Enter fullscreen mode Exit fullscreen mode

Clean, consistent, and maintainable.


5. Overusing @Autowired Field Injection

What I did:

Injected everything using @Autowired on fields.

@Autowired
private UserService userService;
Enter fullscreen mode Exit fullscreen mode

Why it's a problem:

  • Makes dependencies hard to track
  • Breaks immutability
  • Difficult to write unit tests

What I do now:

Use constructor injection:

@Service
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }
}
Enter fullscreen mode Exit fullscreen mode

Bonus: With one constructor, Spring injects it automatically—no @Autowired needed.


6. Forgetting API Documentation (like Swagger/OpenAPI)

What I did:

Left API documentation in someone's Notion page or none at all.

Why it's a problem:

  • Frontend teams have to guess request/response formats
  • No way to test endpoints quickly
  • Becomes a bottleneck during integration

What I do now:

Use springdoc-openapi to generate live docs with minimal setup:

<!-- build.gradle / pom.xml -->
<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-ui</artifactId>
  <version>1.6.14</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Then access Swagger UI at:

http://localhost:8080/swagger-ui/index.html

It reflects your controller annotations and helps both devs and testers.


7. Mixing DTOs with Entities

What I did:

Used JPA entities directly in request bodies and responses.

Why it's a problem:

  • Leaks internal persistence logic
  • Adds accidental complexity (lazy loading, cascade issues)
  • Makes refactoring risky

What I do now:

Create dedicated DTO classes:

public class UserDto {
    private String name;
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

Use mappers like MapStruct, ModelMapper, or just write a utility:

public UserDto toDto(User user) {
    return new UserDto(user.getName(), user.getEmail());
}
Enter fullscreen mode Exit fullscreen mode

Entities stay clean. APIs stay stable.


đź’¬ Final Thoughts

Spring Boot abstracts a lot of complexity—but with that power comes responsibility.

These 7 mistakes cost me time, performance, and headaches. But fixing them made me a better engineer.

What’s one mistake you made in your early Spring Boot journey?

Would love to hear your thoughts below 👇

Top comments (0)