Spring Boot upload file feature | Demo building and testing project

{getToc} $title={Table of Contents}


Spring Boot: Image Upload


Introduction

In this article, we will implement the file upload feature. We will create two endpoints which will upload and get the files respectively. The post request will upload the file. Post request can also be used as a put request.
The get request will get the file.


Body

Project Structure in Spring Tool Suite


project structure


pom.xml file


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>UploadImage</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>UploadImage</name>
	<description>SprintBoot App for upload feature</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.uuid</groupId>
			<artifactId>java-uuid-generator</artifactId>
			<version>4.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>


application.yml

In this application yml, we have created with Database connection by using postgresql database.

spring:
  jpa:
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        format_sql: true
        jdbc.lob.non_contextual_creation: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
        show_sql: true
    defer-datasource-initialization: true
  datasource:
    url: jdbc:postgresql://localhost:5432/image-upload-springboot
    username: postgres
    password: 123456
  
logging:
  level:
    org.springframework: ERROR
    com.example.UploadImage: DEBUG


UploadImageApplication class


package com.example.UploadImage;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UploadImageApplication {

	public static void main(String[] args) {
		SpringApplication.run(UploadImageApplication.class, args);
	}

}

UserController class

This is the controller class. We have two endpoints: 

- Post Endpoint : The post request will upload the file.

- Get Endpoint : The get request will get the file.

package com.example.UploadImage.controller;

import com.example.UploadImage.entity.ProfileUser;
import com.example.UploadImage.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("users")
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping("/upload/post")
    public ResponseEntity<String> uploadProfilePic(@ModelAttribute ProfileUser user) throws Exception {
        log.debug("Inside {} controller", "/users/upload/post");
        userService.updateProfilePicture(user.getUserId(), user.getImageFile());
        return new ResponseEntity<>( "Upload Successful", HttpStatus.OK);
    }

    @GetMapping(value = { "/upload/get" }, produces = MediaType.ALL_VALUE)
    public ResponseEntity<byte[]> getProfilePic(@RequestParam String emailId)  {
        log.debug("Inside {} controller", "/users/upload/get");
        ProfileUser user = userService.getUserDetailsByEmailId(emailId);
        byte[] profilePicBytes = user.getImageByte();
        return new ResponseEntity<>(profilePicBytes, HttpStatus.OK);
    }

}

ProfileUser  Entity class


We have created the profile user entity class which have : userIdemailId and imageFile for requesting data.

package com.example.UploadImage.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.web.multipart.MultipartFile;
import javax.persistence.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "profileuser")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ProfileUser {

    @Id
    @Column(name = "userId")
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String userId;


    @Column(columnDefinition= "VARCHAR", name="emailId")
    private String emailId;

    @JsonIgnore
    @Column(name="imageByte")
    private byte[] imageByte;

    @Transient
    private MultipartFile imageFile;

}


We have used @Table(name = "profileuser")
 
This will create table profileuser with userId, emailId & imageByte columns.

imageFile is just used to read the file which is passed while calling the post request. We convert this file to a byte type variable and store this array of bytes in our db.

During get call, we read these array of bytes and pass this to frontend, so that the bytes are converted to image. For the conversion we use :

produces = MediaType.ALL_VALUE

while defining the “/upload/get” controller. If we know the the MediaType produced by our get endpoint, then we can use a specific mediatype. Like :

produces = MediaType.IMAGE_JPEG_VALUE


UserService Interface

We have created user service interface for upload and get file method using Multipart File. And injected that service in Upload Image Controller class.

package com.example.UploadImage.service;

import com.example.UploadImage.entity.ProfileUser;
import org.springframework.web.multipart.MultipartFile;

public interface UserService {

    void updateProfilePicture(String id, MultipartFile multipartFile) throws Exception;

    ProfileUser getUserDetailsByEmailId(String emailId);
}


UserServiceImpl class

This service implementation class is used to implement service method. In this class we have injected the repository interface too.

package com.example.UploadImage.serviceimpl;

import com.example.UploadImage.entity.ProfileUser;
import com.example.UploadImage.repository.UserRepository;
import com.example.UploadImage.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserRepository userRepository;

    @Override
    public void updateProfilePicture(String userId, MultipartFile multipartFile) throws Exception {
        ProfileUser user = userRepository.findByUserId(userId);
        if(user!= null && multipartFile!=null) {
            user.setImageByte(multipartFile.getBytes());
            userRepository.save(user);
        } else {
            log.debug("User not found for userId : {}", userId);
            throw new Exception("User not found for { userId = " + userId + "}");
        }
    }

    @Override
    public ProfileUser getUserDetailsByEmailId(String emailId) {
        ProfileUser user = userRepository.findByEmailId(emailId);
        return user;
    }
}


UserRepository Interface

We created upload file and get file to do transaction with database in this repository interface. 
In this interface we have extended the JpaRepository interface;
This interface need to be injected by service implemtation class.

package com.example.UploadImage.repository;

import com.example.UploadImage.entity.ProfileUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<ProfileUser, String> {

    ProfileUser findByEmailId(String email);

    ProfileUser findByUserId(String id);

}


Build Project


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  25.277 s
[INFO] Finished at: 2022-08-19T15:49:14+07:00
[INFO] ------------------------------------------------------------------------


Auto create table

After build project successfully, then the table will be auto created.




Create Database with PgAdmin

I have created database name image-upload-springboot as set in the application yml.

image-upload-springboot


Run Project

Go to parent project package -> Right click Run As -> Spring Boot App:

Run project

Successfully started project:




Note: Before you can create post request to upload profile image, you need to insert new profile user infomation with userId and email direct in table.

Due to our post controller for update user image profile by userId, so we need to have user info with userId first.

In this article, we're not implement on create user profile info,  we just focus on upload file feature only.

How to call these endpoints from postman?


Post request : /users/upload/post: http://localhost:8080/users/upload/post


http://localhost:8080/users/upload/post


Result in Table





Get User Profile Info


Get request : /users/upload/get: http://localhost:8080/users/upload/get

Due to my image uploaded is png, then I will change the mediaType my get endpoint in controller like: 

produces = MediaType.IMAGE_PNG_VALUE


http://localhost:8080/users/upload/get


Bytes Conversion of Image :


MultipartFile object has a method called “getBytes()”. This method is used to convert the file to bytes, which can then be stored in the DB. In the below code we just read the file in bytes, and then set it to imageByte (byte[] type object) column/variable of our ProfileUser Entity.

ProfileUser user = userRepository.findByUserId(userId);
if(user!= null && multipartFile!=null) {
    user.setImageByte(multipartFile.getBytes());
    userRepository.save(user);
} else {
    log.debug("User not found for userId : {}", userId);
    throw new Exception("User not found for { userId = " + userId + "}");
}


@ModelAttribute :


In the post request “/users/upload/post” we are using @ModelAttribute instead of @RequestBody -the @ModelAttribute will take a query string. so, all the data are being passed to the server through the url. As for @RequestBody, all the data will be passed to the server through a full JSON body. Image upload wouldn’t be possible with @RequestBody.

Multipart :


Multipart requests combine one or more sets of data into a single body, separated by boundaries. You typically use these requests for file uploads and for transferring data of several types in a single request (for example, a file along with a JSON object).


Conclusion


Hope this article was helpful.


Previous Post Next Post