Implementing Image Self‑Destruct Feature with Spring Boot and MySQL
This guide walks through building a Spring Boot and MySQL‑backed “burn after viewing” image sharing service, covering architecture, environment setup, entity and repository design, upload and automatic deletion logic, Thymeleaf UI with burn animation, error handling, optimization, and Docker deployment.
Introduction
The article describes how to build a "burn after viewing" image sharing feature using Spring Boot, MySQL, and Thymeleaf. The goal is to let users upload images that are automatically deleted after being viewed, enhancing privacy and security.
Background and Requirements
Privacy protection is increasingly important in social media and instant messaging. Users want temporary image sharing without leaving traces. The functional requirements include upload & storage, expiration mechanism, user‑friendly UI, feedback, and security.
Upload and store images securely.
Delete the image automatically after it is viewed.
Provide a simple UI for upload and preview.
Give feedback on upload success/failure.
Ensure the uploaded files cannot be accessed illegally.
System Architecture Design
The system consists of a front‑end (Thymeleaf), a Spring Boot back‑end, a MySQL database, and a file storage layer (local or cloud). The architecture diagram is:
+------------------+
| User Interface |
| (Thymeleaf) |
+--------+---------+
|
+--------v---------+
| Spring Boot |
| Controller |
+--------+---------+
|
+--------v---------+
| Service Layer |
| (Business) |
+--------+---------+
|
+--------v---------+
| Data Access |
| (MySQL/JPA) |
+--------+---------+
|
+--------v---------+
| File Storage |
| (Local/Cloud) |
+------------------+Environment Setup
Create a Spring Boot project with the following dependencies: Spring Web, Spring Data JPA, MySQL Driver, and Thymeleaf. Configure MySQL database image_sharing_db and set connection properties in application.properties .
# MySQL configuration
spring.datasource.url=jdbc:mysql://localhost:3306/image_sharing_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5DialectAdd the MySQL connector dependency in pom.xml :
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>Create a directory uploads with write permission for storing uploaded files.
Feature Implementation
Data Model
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String filename;
private LocalDateTime uploadTime;
private LocalDateTime expirationTime;
// getters and setters omitted for brevity
}Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ImageRepository extends JpaRepository
{}Controller
package com.example.demo.controller;
import com.example.demo.entity.Image;
import com.example.demo.repository.ImageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
@Controller
@RequestMapping("/images")
public class ImageController {
private static final String UPLOAD_DIR = "src/main/resources/static/uploads/";
@Autowired
private ImageRepository imageRepository;
@GetMapping("/upload")
public String uploadPage() { return "upload"; }
@PostMapping("/upload")
public String uploadImage(@RequestParam("file") MultipartFile file, Model model) {
String filename = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
Path path = Paths.get(UPLOAD_DIR + filename);
try {
Files.createDirectories(path.getParent());
file.transferTo(path);
Image image = new Image();
image.setFilename(filename);
image.setUploadTime(LocalDateTime.now());
image.setExpirationTime(LocalDateTime.now().plusMinutes(1));
Image saved = imageRepository.save(image);
model.addAttribute("message", "Image uploaded successfully.");
model.addAttribute("imageUrl", "/uploads/" + filename);
model.addAttribute("imageId", saved.getId());
} catch (IOException e) {
model.addAttribute("message", "Failed to upload image: " + e.getMessage());
}
return "upload";
}
@PostMapping("/burn/{id}")
public ResponseEntity
burnImage(@PathVariable Long id) {
Optional
opt = imageRepository.findById(id);
if (opt.isPresent()) {
Image img = opt.get();
Path path = Paths.get(UPLOAD_DIR + img.getFilename());
try {
Files.deleteIfExists(path);
imageRepository.delete(img);
return ResponseEntity.ok("Image burned successfully");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to burn image");
}
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Image not found");
}
}UI Design
The upload page upload.html uses Thymeleaf to display the upload form, preview the image, and trigger a burn animation via JavaScript.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Burn After Viewing</title>
<style>/* CSS for layout and burn animation */</style>
</head>
<body>
<div class="upload-container">
<h1>Burn After Viewing</h1>
<form action="/images/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</form>
<p th:text="${message}" class="message"></p>
</div>
<div th:if="${imageUrl}" class="preview-container">
<h2>Image Preview</h2>
<img th:src="${imageUrl}" alt="Uploaded Image">
<div id="burnEffect"></div>
</div>
<button id="burnButton">Burn</button>
<script>
document.getElementById('burnButton').onclick = function() {
const burn = document.getElementById('burnEffect');
burn.style.display = 'block';
burn.classList.add('burn');
setTimeout(() => {
document.querySelector('img').style.display = 'none';
burn.style.display = 'none';
alert('Image has been burned and cannot be recovered.');
}, 2000);
};
</script>
</body>
</html>Error Handling
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleRuntimeException(RuntimeException e, Model model) {
model.addAttribute("message", "An error occurred: " + e.getMessage());
return "error";
}
}System Optimization
Performance improvements include image compression (using Thumbnailator) and asynchronous processing with @Async or message queues. Security measures cover filename randomization with UUIDs and MIME‑type validation. Logging is added via SLF4J/Logback.
private static final Logger logger = LoggerFactory.getLogger(ImageController.class);
logger.info("Image uploaded successfully: {}", filename);Testing and Deployment
After functional verification, the application can be containerized with Docker. Example Dockerfile :
FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/image-sharing-app.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]Conclusion
The tutorial demonstrates a complete workflow—from requirement analysis, architecture design, implementation, optimization, to testing and deployment—for building a secure, self‑destructing image sharing service using Spring Boot and MySQL.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.