Sam Nicole Peterson
Sam Nicole Peterson

Reputation: 11

How do I convert MultipartFile to required type byte[]

I'm working with spring Data-Jpa using spring boot. I have a multipart form to upload file to the database with additional fields such as ( firstName, lastName, email, photo). But I keep getting this error message for converting multipart file into required byte[] type. How can I convert the image file from multipart file to byte[] type?

Here is my ControllerClass:

    @PostMapping("/save")
public String saveUserWithPhoto(@ModelAttribute("user") User user,
                         @RequestParam("file") MultipartFile file) throws IOException {
    String fileName = StringUtils.cleanPath(file.getOriginalFilename());

    if (!(fileName == null) || fileName.contains("..")) {       
        byte[] photoBytes = file.getBytes();     
        user.setPhoto(photoBytes);
        userService.saveUserWithPhoto(user);
        return "redirect:/profile";
    }
    .......
    return "redirect:/new-user";

}

Here is my serviceClass:

  @Override
public void saveUserWithPhoto(User theUser){
    userRepository.save(theUser);
}

Here is my EntityClass:

@Entity
@Table(name = "users")
public class User{

    @Id
    @GeneratedValue
    private long id;

    @Column(name = "first_name")
    private String firstName;

   @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    @Lob
    @Column(name="photo")
    private byte[] photo;

}

Upvotes: 1

Views: 14228

Answers (4)

Paul Warren
Paul Warren

Reputation: 2479

As an alternative approach, I would recommend you take a look at the community project Spring Content. Designed to solve exactly your problem, this project provides an abstraction for storage of unstructured data, injects the service and controller implementations for you so that you don't need to implement either yourself and allows you to associate uploaded content with your Spring Data entities.

In addition, all Spring Content storage modules, including JPA, are all stream-based and therefore capable of handling very large files that the byte[] approach won't be able to do.

Adding it to your project would look something like this:

pom.xml (assuming maven)

    <!-- Java API -->
    <!-- just change this depdendency if you want to store somewhere else -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-jpa-boot-starter</artifactId>
        <version>0.8.0</version>
    </dependency>
    <!-- REST API -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-rest-boot-starter</artifactId>
        <version>0.8.0</version>
    </dependency>

Define a content store (same concept as a Spring Data Repository):

UserContentStore.java

  @StoreRestResource(path="photos")
  public interface UserPhotoStore extends ContentStore<User, UUID> {
  }

Associate content with your User entities:

@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue
    private long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Column(name = "email")
    private String email;

    // Spring Content annotations
    @ContentId
    private UUID contentId;

    @ContentLength
    private Long contentLen;

    @MimeType
    private String mimeType;

And that's it. The UserPhotoStore is essentially a generic Spring ResourceLoader. The spring-content-jpa-boot-starter dependency will cause Spring Content to inject a jpa-based implementation of that interface and the spring-content-rest-boot-starter dependency will cause Spring Content to inject an implementation of an @Controller that forwards HTTP requests onto the methods of the UserPhotoStore service.

All up you will now have a fully functional (POST, PUT, GET, DELETE) REST-based file service at /photoso/{userId} that will use your UserPhotoStore to store (and retrieve) files in your database and associate them with your User entities.

So:

curl -F ‘file=@/path/to/local/file.jpg’ /photos/{userId}

will upload /path/to/local/file.jpg, store it in your database and associate it with User entity userId.

And:

curl /photos/{userId}

will retrieve it again.

Content can be associated in various ways as described in the docs (Patterns of Association).

HTH

Upvotes: 1

chas spenlau
chas spenlau

Reputation: 375

I could be mistaken, but I believe Spring is trying to auto-map the entire "User" object that is passed into your endpoint and is failing because there either isn't a "photo" field in the incoming user object, or it is unable to auto-convert that field to a byte[].

For the sake of testing if this is the case or not you can do one of the following 1) If the incoming User object doesn't have a photo field, remove it from your User object 2) If it does and it is coming in as a MultipartFile then change from byte[] to multipartfile.

I know neither of these will help you get it into the database, but it'll help figure out if this is in fact your issue. Then I'd be happy to assist further.

EDIT

Okay, I've been working hard to get this working and at last I got it! See my changes below. Change controller endpoint like so

@PostMapping
@ResponseBody
public void postUser(@ModelAttribute User user, @RequestParam("file") MultipartFile file) throws IOException {
        user.setPhotoByteArray(file.getBytes()); 
       //Do Service Call here(user)
    }

I updated the User object like so.

@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue
    private long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Column(name = "email")
    private String email;
    @Column(name = "photo")
    private byte[] photoByteArray;

    public User(long id, String firstName, String lastName, String email) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

And that's it! I verified this working by sending a form-data request through postman with all the necessary fields, I also added the following lines to save the file in a local directory so i could open it up to confirm that it correctly handled the image that i passed into postman

FileUtils.writeByteArrayToFile(new File("test.png"), user.getPhotoByteArray());

I also added a constructor to your entity that way when it created the object from model attribute it would allow it to start off being null, and i could set it later.

Let me know if you continue to have issues or questions.

Upvotes: 0

AlexGera
AlexGera

Reputation: 783

Spring does not store the bytes in the class itself but force you to open a stream from that.

The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storage will be cleared at the end of request processing.

I typically go with intermediate storage to a file or in memory with the following code:

private final Path rootLocation;
try (InputStream inputStream = file.getInputStream()) {
  byte[] in = StreamUtils.copyToByteArray(inputStream);
  //Files.copy(inputStream, this.rootLocation.resolve(filename),
  //                StandardCopyOption.REPLACE_EXISTING);
}

Of course you can use whatever you like instead to create byte[] from a stream. Could recommend StreamUtils if you are in Spring

Upvotes: 0

Morteza Jalambadani
Morteza Jalambadani

Reputation: 2455

MultipartFile contais a byte array of content file. you can use from getBytes() method.

in application.properties add:

spring.servlet.multipart.enabled=true

or use YAML can add:

spring:
  servlet:
    multipart:
      enabled: true

and can get byte array from MultipartFile Like:

MultipartFile  file;
//for your segment file fill from client side(in request)
byte [] content = file.getBytes();

Upvotes: 2

Related Questions