Top 10 Mistakes in Spring Framework and How to Avoid Them (With Examples)

The Spring Framework is one of the most powerful and widely used frameworks in Java development. However, even experienced developers often make common mistakes that lead to performance issues, security vulnerabilities, and maintainability problems.

In this article, we'll explore the top 10 mistakes developers make while using Spring Framework and provide practical solutions with code examples to avoid them.

Let’s dive in! 🚀

1️⃣ Not Using Dependency Injection Properly

Bad Code (Manual Object Creation)

public class UserService {
    private UserRepository userRepository = new SimpleUserRepository(); // ❌ Manual object creation
}

🔴 Issue: This creates a tight coupling between classes, making unit testing difficult and violating the dependency inversion principle.

Good Code (Using Spring Dependency Injection)

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Why is this better?
✔ Promotes loose coupling, making it easier to test and maintain
✔ Supports dependency injection, which enhances flexibility

2️⃣ Using Field Injection Instead of Constructor Injection

Bad Code (Field Injection - Harder to Test)

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
}

🔴 Issue: Field injection does not allow immutable dependencies and makes unit testing difficult because mock dependencies cannot be injected easily.

@Service
public class OrderService {
    private final OrderRepository orderRepository;

    @Autowired
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

Why is this better?
✔ Ensures dependency availability and immutability
✔ Makes testing easier since dependencies can be injected through the constructor

3️⃣ Ignoring @Transactional in Database Operations

Bad Code (No Transaction Management - Risk of Partial Updates)

public void transferMoney(Long fromAccount, Long toAccount, Double amount) {
    accountRepository.debit(fromAccount, amount);
    accountRepository.credit(toAccount, amount);
}

🔴 Issue: If an exception occurs after debiting, the transaction isn't rolled back, leading to data inconsistency.

Good Code (Using @Transactional)

@Service
public class BankService {

    @Transactional
    public void transferMoney(Long fromAccount, Long toAccount, Double amount) {
        accountRepository.debit(fromAccount, amount);
        accountRepository.credit(toAccount, amount);
    }
}

Why is this better?
✔ Ensures data consistency by automatically rolling back on exceptions
✔ Saves developers from writing manual rollback logic

4️⃣ Using @RestController Without Proper Exception Handling

Bad Code (No Exception Handling - Returns 500 Internal Server Error)

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id); // ❌ Throws 500 error if user not found
    }
}

🔴 Issue: Without exception handling, the API returns generic errors instead of meaningful responses.

Good Code (Using @ControllerAdvice for Global Exception Handling)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleNotFound(ResourceNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

Why is this better?
✔ Provides consistent error responses for better API usability
✔ Enhances API maintainability and debugging

5️⃣ Not Configuring Spring Security Properly

Bad Code (No Authentication & Authorization)

@GetMapping("/users")
public List<User> getUsers() {  
    return userService.getAllUsers(); // ❌ Open to anyone  
}

🔴 Issue: Without authentication, anyone can access sensitive data, leading to security vulnerabilities.

Good Code (Use Spring Security & OAuth2 + JWT)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        return http.build();
    }
}

Why is this better?
✔ Ensures API endpoints are secured with OAuth2 authentication
✔ Prevents unauthorized access to sensitive resources

6️⃣ Not Closing Database Connections Properly

Bad Code (Not Closing Resources Manually)

public List<User> getUsers() throws SQLException {
    Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
    
    // ❌ Connection leak if not closed properly
}

🔴 Issue: This leaks database connections, which can exhaust database resources over time.

Good Code (Using Spring Data JPA - Connection Managed Automatically)

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

Why is this better?
Automatically manages database connections, preventing leaks
✔ Reduces boilerplate code

7️⃣ Not Optimizing Hibernate Queries (N+1 Problem)

Bad Code (Fetching Data Inefficiently - Causes N+1 Queries)

@OneToMany(mappedBy = "user")
private List<Order> orders;

🔴 Issue: Fetching users also fetches orders separately, leading to multiple queries.

Good Code (Using @EntityGraph to Optimize Queries)

@EntityGraph(attributePaths = {"orders"})
@Query("SELECT u FROM User u WHERE u.id = :id")
User findUserWithOrders(@Param("id") Long id);

Why is this better?
✔ Reduces query overhead and improves performance

8️⃣ Using RestTemplate Instead of RestClient (Deprecated API)

Bad Code (Using RestTemplate)

RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject("https://api.example.com/user/1", User.class);

🔴 Issue: RestTemplate is deprecated in the newer version of Spring Framework.

Good Code (Using RestClient)

RestClient restClient = RestClient.builder().baseUrl("https://api.example.com").build();
User user = restClient.get().uri("/user/1").retrieve().body(User.class);

Why is this better?
More modern & efficient API

9️⃣ Not Using Caching for Expensive Queries

Bad Code (Repeated Expensive Database Calls)

public List<Product> getAllProducts() {
    return productRepository.findAll();  
}

🔴 Issue: Increases database load and slows down response times.

Good Code (Using Spring Cache with Redis)

@Cacheable("products")
public List<Product> getAllProducts() {
    return productRepository.findAll();
}

Why is this better?
Improves performance

🔟 Hardcoding Configuration Instead of Using application.properties

Bad Code (Hardcoded Values)

public static final String API_URL = "https://api.example.com";  

🔴 Issue: Hardcoding makes changes difficult.

Good Code (Using @Value)

@Value("${api.url}")
private String apiUrl;

Why is this better?
Easier to manage configurations

🎯 Conclusion

Avoiding these common Spring Framework mistakes will help you write cleaner, more efficient, and secure applications.

Key Takeaways:
Use dependency injection properly
Handle transactions & exceptions carefully
Use modern Spring features like RestClient & SecurityFilterChain
Optimize database queries & caching

By following these best practices, you'll build better, scalable, and maintainable Spring applications! 🚀

Happy Coding with Spring Framework! 🌱

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare