Backend Development 19 min read

Implementing a Snapchat-like Self-Destructing Image Feature with Spring Boot and MySQL

This article presents a step‑by‑step guide on building a Snapchat‑style “burn after reading” image sharing system using Spring Boot, MySQL, Thymeleaf, and optional cloud storage, covering background analysis, architecture design, environment setup, code implementation, optimization, testing, and deployment.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Implementing a Snapchat-like Self-Destructing Image Feature with Spring Boot and MySQL

1. Background and Requirement Analysis

Privacy protection and information security have become increasingly important. With the widespread use of social media and instant‑messaging tools, many users want to share temporary content that leaves no trace. The self‑destructing image feature meets this privacy demand and enhances security.

1.1 Current State of Internet Privacy Protection

As the internet matures, users are more aware of privacy risks. Frequent data‑leak incidents on social platforms have driven users to seek ways to limit the lifespan of shared images, a need that applies to personal, enterprise, educational, and other scenarios.

1.2 Requirements for Self‑Destructing Images

The feature should include the following requirements:

Upload and Storage : Users can upload images, which must be stored securely.

Expiration Mechanism : Images are automatically deleted after being viewed.

User‑Friendly Interface : Provide a simple and intuitive UI for uploading and viewing.

Feedback Mechanism : Show status messages such as upload success or failure.

Security : Ensure uploaded images cannot be accessed illegally.

2. System Architecture Design

2.1 Technology Stack

The system uses the following stack:

Backend : Spring Boot – rapid development of RESTful APIs with good extensibility.

Database : MySQL – relational database suitable for structured data storage and queries.

Frontend : Thymeleaf + HTML/CSS/JavaScript – template engine for dynamic page generation.

File Storage : Local file system or cloud storage (e.g., AWS S3) for flexible file handling.

2.2 System Architecture Diagram

+------------------+
|   User Interface |
|   (Thymeleaf)    |
+--------+---------+
         |
+--------v---------+
|   Spring Boot    |
|   Controller     |
+--------+---------+
         |
+--------v---------+
|   Service Layer  |
|   (Business)     |
+--------+---------+
         |
+--------v---------+
| Data Access Layer|
|   (MySQL/JPA)    |
+--------+---------+
         |
+--------v---------+
|   File Storage   |
| (Local/Cloud)    |
+------------------+

3. Environment Setup

3.1 Create Spring Boot Project

Use Spring Initializr (start.spring.io) to generate a new project with the following dependencies:

Spring Web – for building RESTful APIs.

Spring Data JPA – simplifies data‑access layer development.

MySQL Driver – connects to MySQL.

Thymeleaf – generates dynamic web pages.

Import the generated project into an IDE (IntelliJ IDEA or Eclipse) and ensure it compiles and runs.

3.2 Database Configuration

Create a new MySQL database, for example image_sharing_db :

CREATE DATABASE image_sharing_db;

Configure the connection in application.properties :

# MySQL database 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.MySQL5Dialect

3.3 Add Dependencies

Ensure the following dependency is present in pom.xml :

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

3.4 File Storage Directory

Create a folder named uploads in the project root and grant write permissions so that uploaded images can be saved.

4. Feature Implementation

4.1 Data Model Design

Create an Image entity to store image metadata such as filename, upload time, and expiration time.

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
public class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Image ID
    private String filename; // File name
    private LocalDateTime uploadTime; // Upload time
    private LocalDateTime expirationTime; // Expiration time

    // getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }
    public LocalDateTime getUploadTime() { return uploadTime; }
    public void setUploadTime(LocalDateTime uploadTime) { this.uploadTime = uploadTime; }
    public LocalDateTime getExpirationTime() { return expirationTime; }
    public void setExpirationTime(LocalDateTime expirationTime) { this.expirationTime = expirationTime; }
}

4.2 Data Access Layer

Create a repository interface ImageRepository that extends JpaRepository for basic CRUD operations.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ImageRepository extends JpaRepository
{
}

4.3 Controller Implementation

Implement ImageController to handle image upload, view, and burn requests.

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;

/**
 * By zhangT
 */
@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"; // return upload page view
    }

    @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);
            // Save file info to DB
            Image image = new Image();
            image.setFilename(filename);
            image.setUploadTime(LocalDateTime.now());
            image.setExpirationTime(LocalDateTime.now().plusMinutes(1)); // expire after 1 minute
            Image savedImage = imageRepository.save(image);
            model.addAttribute("message", "Image Uploaded Successfully.");
            model.addAttribute("imageUrl", "/uploads/" + filename);
            model.addAttribute("imageId", savedImage.getId());
        } catch (IOException e) {
            model.addAttribute("message", "Failed to upload image: " + e.getMessage());
        }
        return "upload"; // return upload page view
    }

    @PostMapping("/burn/{id}")
    public ResponseEntity
burnImage(@PathVariable Long id) {
        Optional
imageOptional = imageRepository.findById(id);
        if (imageOptional.isPresent()) {
            Image image = imageOptional.get();
            Path path = Paths.get("src/main/resources/static/uploads/" + image.getFilename());
            try {
                Files.deleteIfExists(path);
                imageRepository.delete(image);
                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");
    }
}

4.4 User Interface Design

4.4.1 Upload Page

The upload.html page allows users to upload an image, preview it, and trigger the burn effect.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Self‑Destructing Image</title>
    <style>
        body {font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center;}
        .upload-container {margin-top: 50px; text-align: center;}
        .preview-container img {max-width: 100%; height: auto; margin-top: 10px;}
        .message {color: green; font-weight: bold; margin-top: 10px;}
        .burn {animation: burn 1s forwards;}
        @keyframes burn {from {opacity: 1;} to {opacity: 0; transform: scale(1.5);}}
        #imageContainer {position: relative;}
        #burnEffect {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255,0,0,0.5); display: none; z-index: 10;}
    </style>
</head>
<body>
    <div class="upload-container">
        <h1>Self‑Destructing Image</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 id="imageContainer" class="preview-container" th:if="${imageUrl}">
        <h2>Image Preview</h2>
        <img th:src="${imageUrl}" alt="Uploaded Image">
        <div id="burnEffect"></div>
    </div>
    <button id="burnButton">Burn After Reading</button>
    <script>
        document.getElementById("burnButton").onclick = function() {
            const burnEffect = document.getElementById("burnEffect");
            burnEffect.style.display = "block";
            burnEffect.classList.add("burn");
            setTimeout(function() {
                document.querySelector("img").style.display = "none";
                burnEffect.style.display = "none";
                alert("Image has been burned and cannot be recovered.");
            }, 2000);
        };
        // Optional server‑side burn request (commented out)
        // const imageId = /*[[${imageId}]]*/ 0;
        // fetch(`/burn/${imageId}`, {method: 'POST'}).then(...);
    </script>
</body>
</html>

4.5 Error Handling

Use a global exception handler to capture runtime errors and return an error view.

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"; // return error page view
    }
}

5. System Optimization

5.1 Performance Optimization

Image Compression : Compress images on upload using libraries such as Thumbnailator.

Asynchronous Processing : Offload image handling to async methods or message queues using @Async.

5.2 Security

Filename Safety : Rename uploaded files with UUIDs to avoid collisions and security issues.

File Type Checking : Verify that the uploaded file is an image (e.g., content type starts with "image/").

String contentType = file.getContentType();
if (!contentType.startsWith("image/")) {
    model.addAttribute("message", "Please upload a valid image file.");
    return "upload";
}

5.3 Logging

Integrate SLF4J and Logback to record upload, view, and error events.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ImageController {
    private static final Logger logger = LoggerFactory.getLogger(ImageController.class);
    // Example log
    logger.info("Image uploaded successfully: {}", filename);
}

6. Testing and Deployment

6.1 Effect Verification

The following GIF demonstrates the burn‑after‑reading effect:

6.2 Deployment

Containerize the Spring Boot application with Docker for consistent deployment across environments.

FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/image-sharing-app.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

7. Conclusion

This demo illustrates how to build a secure, user‑friendly self‑destructing image sharing platform using Spring Boot and MySQL, covering requirement analysis, architecture design, implementation, optimization, testing, and deployment.

Javabackend developmentSpring BootMySQLFile UploadWeb DevelopmentSelf-Destructing Images
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.