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.