Spring Boot Backend Development

Backend April 2025 Mohsen Mashkour
Spring Boot Backend Development

Introduction

Spring Boot is a powerful Java-based framework for building production-ready backend applications. It simplifies the development of stand-alone, production-grade Spring-based applications with minimal configuration. For Android developers, understanding Spring Boot is essential for creating robust backend services that power mobile applications.

This comprehensive guide will take you from basic Spring Boot concepts to advanced features like REST APIs, database integration, security, and deployment. Whether you're building a simple API for your Android app or a complex microservices architecture, Spring Boot provides the tools and patterns you need for successful backend development.

Why Spring Boot for Android Backend?

  • Rapid Development: Auto-configuration and starter dependencies
  • Production Ready: Built-in monitoring, metrics, and health checks
  • RESTful APIs: Easy creation of REST endpoints for mobile apps
  • Database Integration: Seamless integration with various databases
  • Security: Built-in security features and OAuth2 support
  • Microservices: Perfect for microservices architecture
  • Cloud Ready: Easy deployment to cloud platforms
  • Large Ecosystem: Extensive community and documentation

Setting Up Spring Boot

Project Setup


    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-validation
    
Main Application Class
@SpringBootApplication
public class AndroidBackendApplication {
    public static void main(String[] args) {
        SpringApplication.run(AndroidBackendApplication.class, args);
    }
}

REST API Development

Controller Example
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping
    public ResponseEntity> getAllUsers() {
        List users = userService.findAll();
        return ResponseEntity.ok(users);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
        return ResponseEntity.ok(user);
    }
    
    @PostMapping
    public ResponseEntity createUser(@Valid @RequestBody CreateUserRequest request) {
        User user = userService.createUser(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity updateUser(@PathVariable Long id, 
                                         @Valid @RequestBody UpdateUserRequest request) {
        User user = userService.updateUser(id, request);
        return ResponseEntity.ok(user);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

Database Integration

Entity Class
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }
    
    // Getters, setters, constructors
}
Repository Interface
@Repository
public interface UserRepository extends JpaRepository {
    Optional findByEmail(String email);
    List findByNameContainingIgnoreCase(String name);
    boolean existsByEmail(String email);
}

Service Layer

@Service
@Transactional
public class UserService {
    
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    
    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }
    
    public List findAll() {
        return userRepository.findAll();
    }
    
    public Optional findById(Long id) {
        return userRepository.findById(id);
    }
    
    public User createUser(CreateUserRequest request) {
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new EmailAlreadyExistsException("Email already exists");
        }
        
        User user = new User();
        user.setEmail(request.getEmail());
        user.setName(request.getName());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        
        return userRepository.save(user);
    }
    
    public User updateUser(Long id, UpdateUserRequest request) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
        
        user.setName(request.getName());
        return userRepository.save(user);
    }
    
    public void deleteUser(Long id) {
        if (!userRepository.existsById(id)) {
            throw new UserNotFoundException("User not found");
        }
        userRepository.deleteById(id);
    }
}

Security Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

JWT Authentication

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    private final AuthService authService;
    
    @PostMapping("/login")
    public ResponseEntity login(@Valid @RequestBody LoginRequest request) {
        AuthResponse response = authService.login(request);
        return ResponseEntity.ok(response);
    }
    
    @PostMapping("/register")
    public ResponseEntity register(@Valid @RequestBody RegisterRequest request) {
        AuthResponse response = authService.register(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

@Service
public class AuthService {
    
    private final UserService userService;
    private final JwtTokenProvider jwtTokenProvider;
    private final PasswordEncoder passwordEncoder;
    
    public AuthResponse login(LoginRequest request) {
        User user = userService.findByEmail(request.getEmail())
            .orElseThrow(() -> new AuthenticationException("Invalid credentials"));
        
        if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
            throw new AuthenticationException("Invalid credentials");
        }
        
        String token = jwtTokenProvider.generateToken(user);
        return new AuthResponse(token, user);
    }
}

Exception Handling

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(EmailAlreadyExistsException.class)
    public ResponseEntity handleEmailExists(EmailAlreadyExistsException ex) {
        ErrorResponse error = new ErrorResponse("EMAIL_EXISTS", ex.getMessage());
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handleValidation(MethodArgumentNotValidException ex) {
        List errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
        
        ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", "Validation failed", errors);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}

Configuration Properties

# application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/android_backend
    username: postgres
    password: password
    driver-class-name: org.postgresql.Driver
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true

  security:
    jwt:
      secret: your-secret-key-here
      expiration: 86400000 # 24 hours

server:
  port: 8080
  servlet:
    context-path: /api

logging:
  level:
    com.example.androidbackend: DEBUG
    org.springframework.security: DEBUG

Testing

@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void shouldReturnAllUsers() {
        // Given
        User user = new User();
        user.setEmail("test@example.com");
        user.setName("Test User");
        userRepository.save(user);
        
        // When
        ResponseEntity> response = restTemplate.exchange(
            "/api/users",
            HttpMethod.GET,
            null,
            new ParameterizedTypeReference>() {}
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).hasSize(1);
        assertThat(response.getBody().get(0).getEmail()).isEqualTo("test@example.com");
    }
}

Deployment

Docker Configuration
FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Docker Compose
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/android_backend
      - SPRING_DATASOURCE_USERNAME=postgres
      - SPRING_DATASOURCE_PASSWORD=password
    depends_on:
      - db
  
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=android_backend
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Best Practices

  • Use DTOs: Separate API models from entity models
  • Implement Validation: Validate all input data
  • Handle Exceptions: Provide meaningful error messages
  • Use Logging: Implement proper logging throughout the application
  • Security First: Always implement proper authentication and authorization
  • Database Optimization: Use proper indexing and query optimization
  • API Documentation: Use Swagger/OpenAPI for API documentation
  • Monitoring: Implement health checks and metrics

Practice Exercises

Exercise 1: User Management API
// Create a complete user management system with:
// - User registration and authentication
// - CRUD operations for users
// - Role-based access control
// - Email verification
// - Password reset functionality
Exercise 2: Social Media Backend
// Build a social media backend with:
// - User profiles and posts
// - Follow/unfollow functionality
// - Like and comment system
// - Real-time notifications
// - File upload for images
Exercise 3: E-commerce API
// Create an e-commerce backend with:
// - Product catalog and categories
// - Shopping cart management
// - Order processing
// - Payment integration
// - Inventory management

Next Steps

  • Microservices: Break down monolithic applications
  • Message Queues: Implement asynchronous processing with RabbitMQ/Kafka
  • Caching: Use Redis for performance optimization
  • GraphQL: Implement GraphQL APIs for flexible data fetching
  • Cloud Deployment: Deploy to AWS, Azure, or Google Cloud
  • CI/CD: Implement continuous integration and deployment

Resources

Summary

Spring Boot provides a powerful and flexible foundation for building backend services that can support Android applications. Its auto-configuration, embedded servers, and extensive ecosystem make it an excellent choice for both simple APIs and complex microservices architectures.

You've learned about Spring Boot setup, REST API development, database integration, security, testing, and deployment. Remember to follow best practices, implement proper security measures, and design your APIs with mobile applications in mind.

Practice building different types of backend services, experiment with advanced features, and stay updated with the latest Spring Boot patterns and best practices. The more you work with Spring Boot, the more you'll appreciate its power and flexibility for building robust, scalable backend systems.