Spring Boot Development Guide

Essential patterns, troubleshooting, and best practices for Spring Boot applications

Spring Boot has revolutionized Java development by providing auto-configuration, embedded servers, and production-ready features out of the box. This guide covers advanced patterns, common pitfalls, and practical solutions that experienced developers encounter in real-world Spring Boot applications.

๐Ÿ› Common Spring Boot Issues

Frequent Error Messages:
โ€ข APPLICATION FAILED TO START - Port 8080 was already in use
โ€ข Failed to configure a DataSource: 'url' attribute is not specified
โ€ข No qualifying bean of type found for dependency injection
โ€ข Whitelabel Error Page - This application has no explicit mapping
โ€ข Unable to start embedded Tomcat server

Application Startup Failures

Database Configuration Issues

One of the most common startup failures involves database connectivity:

# Check if database server is running
mysql -u root -p -h localhost
psql -h localhost -U postgres -d mydb
# application.properties - MySQL example
spring.datasource.url=jdbc:mysql://localhost:3306/myapp
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

Dependency Injection Errors

โš ๏ธ Common DI Mistakes:
  • Forgetting @Component, @Service, or @Repository annotations
  • Circular dependencies between beans
  • Missing @SpringBootApplication or incorrect package scanning
  • Using @Autowired on static fields
// โŒ Wrong: Missing annotation
public class UserService {
    // This won't be managed by Spring
}

// โœ… Correct: Proper annotation
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

๐Ÿ—๏ธ Project Structure & Architecture

Recommended Package Structure

src/main/java/com/company/myapp/
โ”œโ”€โ”€ MyAppApplication.java          // Main class
โ”œโ”€โ”€ config/                        // Configuration classes
โ”‚   โ”œโ”€โ”€ SecurityConfig.java
โ”‚   โ””โ”€โ”€ DatabaseConfig.java
โ”œโ”€โ”€ controller/                    // REST controllers
โ”‚   โ”œโ”€โ”€ UserController.java
โ”‚   โ””โ”€โ”€ ProductController.java
โ”œโ”€โ”€ service/                       // Business logic
โ”‚   โ”œโ”€โ”€ UserService.java
โ”‚   โ””โ”€โ”€ ProductService.java
โ”œโ”€โ”€ repository/                    // Data access layer
โ”‚   โ”œโ”€โ”€ UserRepository.java
โ”‚   โ””โ”€โ”€ ProductRepository.java
โ”œโ”€โ”€ model/                         // Entity classes
โ”‚   โ”œโ”€โ”€ User.java
โ”‚   โ””โ”€โ”€ Product.java
โ”œโ”€โ”€ dto/                          // Data transfer objects
โ”‚   โ”œโ”€โ”€ UserDto.java
โ”‚   โ””โ”€โ”€ ProductDto.java
โ””โ”€โ”€ exception/                    // Custom exceptions
    โ””โ”€โ”€ GlobalExceptionHandler.java

Controller Layer

Handle HTTP requests and responses. Keep controllers thin.

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public ResponseEntity<List<UserDto>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }
    
    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
        UserDto created = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Service Layer

Contains business logic and coordinates between layers.

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public UserDto createUser(CreateUserRequest request) {
        User user = new User(request.getName(), request.getEmail());
        User saved = userRepository.save(user);
        return UserMapper.toDto(saved);
    }
    
    public List<UserDto> getAllUsers() {
        return userRepository.findAll()
            .stream()
            .map(UserMapper::toDto)
            .collect(Collectors.toList());
    }
}

Repository Layer

Data access and persistence operations.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    List<User> findByEmailContaining(String email);
    
    @Query("SELECT u FROM User u WHERE u.active = true")
    List<User> findActiveUsers();
    
    Optional<User> findByEmail(String email);
    
    boolean existsByEmail(String email);
}

๐Ÿ—„๏ธ Database Integration & JPA

Entity Design Best Practices

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 100)
    private String name;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    @UpdateTimestamp
    private LocalDateTime updatedAt;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();
    
    // Constructors, getters, setters...
}
Database JDBC URL Pattern Driver Class MySQL jdbc:mysql://localhost:3306/dbname com.mysql.cj.jdbc.Driver PostgreSQL jdbc:postgresql://localhost:5432/dbname org.postgresql.Driver H2 (Memory) jdbc:h2:mem:testdb org.h2.Driver SQL Server jdbc:sqlserver://localhost:1433;databaseName=dbname com.microsoft.sqlserver.jdbc.SQLServerDriver

Database Migration with Flyway

Manage database schema changes systematically:

# Add Flyway dependency to pom.xml
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>
-- src/main/resources/db/migration/V1__Create_users_table.sql
CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

๐Ÿ”’ Security & Authentication

Spring Security Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable());
        
        return http.build();
    }
}
โš ๏ธ Security Best Practices:
  • Never store passwords in plain text - use BCrypt
  • Implement proper CORS configuration for frontend integration
  • Use HTTPS in production environments
  • Validate and sanitize all user inputs
  • Implement rate limiting for API endpoints

JWT Token Implementation

@Service
public class JwtService {
    
    private final String SECRET_KEY = "your-secret-key";
    private final long EXPIRATION_TIME = 86400000; // 24 hours
    
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
            .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
            .compact();
    }
    
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }
}

๐Ÿงช Testing Spring Boot Applications

Unit Testing

Test individual components in isolation.

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void createUser_ShouldReturnUserDto() {
        // Given
        CreateUserRequest request = new CreateUserRequest("John", "[email protected]");
        User savedUser = new User("John", "[email protected]");
        
        when(userRepository.save(any(User.class))).thenReturn(savedUser);
        
        // When
        UserDto result = userService.createUser(request);
        
        // Then
        assertThat(result.getName()).isEqualTo("John");
        verify(userRepository).save(any(User.class));
    }
}

Integration Testing

Test complete request-response cycles.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserControllerIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void createUser_ShouldReturnCreatedUser() {
        // Given
        CreateUserRequest request = new CreateUserRequest("Jane", "[email protected]");
        
        // When
        ResponseEntity<UserDto> response = restTemplate.postForEntity(
            "/api/users", request, UserDto.class
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().getEmail()).isEqualTo("[email protected]");
    }
}

Repository Testing

Test data access layer with @DataJpaTest.

@DataJpaTest
class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void findByEmail_ShouldReturnUser() {
        // Given
        User user = new User("Test", "[email protected]");
        entityManager.persistAndFlush(user);
        
        // When
        Optional<User> found = userRepository.findByEmail("[email protected]");
        
        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Test");
    }
}
๐Ÿ’ก Testing Best Practices:
  • Use @MockBean for Spring context mocking
  • Test with profiles (test, integration) for different configurations
  • Use TestContainers for real database testing
  • Write tests that verify business logic, not just technical implementation

โšก Performance Optimization

Database Performance

  • Connection Pooling: Configure HikariCP properly
  • Query Optimization: Use @Query for complex queries
  • Lazy Loading: Avoid N+1 query problems
  • Caching: Implement Redis or Caffeine cache
# HikariCP configuration
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.connection-timeout=20000

Application Monitoring

# Enable Actuator endpoints
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true

Key Metrics to Monitor:

  • Response times and throughput
  • Database connection pool usage
  • Memory and CPU utilization
  • Error rates and exception counts