reactjsspringaxiosmultipartform-datamultipart

Not Possible to upload images using React, Spring gives a error MultipartException: Current request is not a multipart request


I can not solve the issue with MultipartException? I try to create a new Item with adding the imges React. Every time I receive the error in Spring console, that current request is not Multipart request.

I created a form for adding the item details and images of item and added enctype="multipart/form-data" but it shows the Axios error AxiosError {message: 'Request failed with status code 500', name: 'AxiosError', code: 'ERR_BAD_RESPONSE', config: {…}, request: XMLHttpRequest, …

but Spring console shows me :

org.springframework.web.multipart.MultipartException: Current request is not a multipart request

On spring I created two enities for Items and Images. And on Item add the images should be added together with item ID. For I can take the images from DB by Item id.

Here are the Entities:

Items Entity :

package com.demo.fijinv.Models;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "assets")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Assets {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    @Column(name = "asset_type")
    private String assettype;
    @Column(name = "asset_brand")
    private String assetBrand;
    @Column(name = "asset_model")
    private String assetModel;


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "assets")
    private List<Image> images = new ArrayList<>();


    private Long previewImageId;
    private LocalDateTime creationDate;
    @PrePersist //to read about inversion of control
    private void init() {
        creationDate = LocalDateTime.now();
    }
    public void addImageToItemName(Image image) {
        image.setAssets(this);
        images.add(image);
    }

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Items> invItem = new ArrayList<>();
}

Images Entity:

package com.demo.fijinv.Models;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
@Entity
@Table(name = "images")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "originalFileName")
    private String originalFileName;
    @Column(name = "size")
    private Long size;
    @Column(name = "contentType")
    private String contentType;
    @Column(name = "isPreviewImage")
    private boolean isPreviewImage;
    @Column(length = 10000000)
    @Lob
    private byte[] bytes;

    @ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    private Assets assets;

    @ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    private List<Items> items;
}

And the controllers for these entities:

Items Controller :

package com.demo.fijinv.Conteollers;

import com.demo.fijinv.Models.Assets;
import com.demo.fijinv.Models.Image;
import com.demo.fijinv.Repositories.AssetsRepository;
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.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@CrossOrigin("*")
@RestController
@RequestMapping("/api/assets")
public class AssetsController {
    @Autowired
    public AssetsRepository assetsRepository;

    @GetMapping
    public List<Assets> getAllAssets(){
        return assetsRepository.findAll();
    }

    @DeleteMapping("{id}")
    public ResponseEntity<Assets> deleteAsset(@PathVariable Long id){
        assetsRepository.deleteById(id);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @PostMapping(consumes = {MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public void saveAsset(@RequestBody Assets assets, @RequestParam("file1") MultipartFile file1, @RequestParam("file2") MultipartFile file2, @RequestParam("file3") MultipartFile file3) throws IOException {
        Image image1;
        Image image2;
        Image image3;

        if(file1.getSize() != 0){
            image1 = toImageEntity(file1);
            image1.setPreviewImage(true);
            assets.addImageToItemName(image1);
        }
        if(file2.getSize() != 0){
            image2 = toImageEntity(file2);
            assets.addImageToItemName(image2);
        }
        if(file3.getSize() != 0){
            image3 = toImageEntity(file3);
            assets.addImageToItemName(image3);
        }

        Assets itemFromDB = assetsRepository.save(assets);
        itemFromDB.setPreviewImageId(itemFromDB.getImages().get(0).getId());
        assetsRepository.save(assets);
    }

    private Image toImageEntity(MultipartFile file) throws IOException {
        Image image = new Image();
        image.setName(file.getName());
        image.setOriginalFileName(file.getOriginalFilename());
        image.setContentType(file.getContentType());
        image.setSize(file.getSize());
        image.setBytes(file.getBytes());
        return image;
    }
}

Images Controller :

package com.demo.fijinv.Conteollers;

import com.demo.fijinv.Models.Image;
import com.demo.fijinv.Repositories.ImageRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayInputStream;
import java.util.List;

@CrossOrigin("*")
@RestController //no needed to present anything
@RequestMapping("/api/assets/images")
public class ImageController {
    @Autowired
    public ImageRepository imageRepository;

    @GetMapping
    private List<Image> getAllImages(){
        return imageRepository.findAll();
    }

    @GetMapping("{id}")
    private ResponseEntity<?> getImageByID(@PathVariable Long id){
        Image image = imageRepository.findById(id).orElse(null);
        return ResponseEntity.ok()
                .header("filename", image.getOriginalFileName())
                .contentType(MediaType.valueOf(image.getContentType()))
                .contentLength(image.getSize())
                .body(new InputStreamResource(new ByteArrayInputStream(image.getBytes())));
    }
}

Both controllers are configured for allow the requests from all the services by adding @CrossOrigin("*"), which means that spring should receive the requests.

With React I created a simple page with form in modal where I scan add the item with images :

import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import { Modal, Button } from 'react-bootstrap'
import AssetsSetvice from "./../../Services/AssetsService"


const AssetsComponenet = () => {
    const [show, setShow] = useState(false);
    const modalShow = () => setShow(true);
    const modalHide = () => setShow(false);

    // getting all assets 
    const [assets, setAssets] = useState([]);
    useEffect(() => {
        AssetsSetvice.getAllAssets().then((res) => {
            setAssets(res.data);
        }).catch(err => {
            console.log(err)
        })
    }, []);
    const [assettype, setAssettype] = useState('');
    const [assetBrand, setAssetBrand] = useState('');
    const [assetModel, setAssetModel] = useState('');
    const [image1, setImage1] = useState();
    const [image2, setImage2] = useState();
    const [image3, setImage3] = useState();
    const [showImage1, setShowImage1] = useState();
    const [showImage2, setShowImage2] = useState();
    const [showImage3, setShowImage3] = useState();

    const saveAsset = (event) => {
        event.preventDefault()
        const asset = {
            assettype,
            assetBrand,
            assetModel,
            image1,
            image2,
            image3
        }

        console.log(asset)
        
        AssetsSetvice.addNewAsset(asset).then((res) => {
            console.log(res.data)
            event.preventDefault();
        }).catch(err => {
            console.log(err)
        })
    }

    const saveAndClose = (ev) => {
        saveAsset(ev);
        setShow(false);
    }

    const deleteAsset = (id) => {
        if (window.confirm("Are you sure want to delete this asset?")) {
            AssetsSetvice.deleteAsset(id).then((response) => {
                console.log(`Asset with ${id} was deleted`);
                // window.location.replace("/users");
            }).catch(error => {
                console.log(`Something went worng : \n ${error}`);
            })
        }
    }

    let count = 1;
    return (
        <>
            <div className='container'>
                <h2 className='text-center mt-4 mb-4 bold'> List of Items</h2>
                <div className='mb-3'>
                    <button type='button' className='btn btn-primary' data-toggle="modal" onClick={modalShow} data-target="#addNewItemModal">
                        Add new Asset
                    </button>
                </div>
                <table className='table table-bordered table-striped'>
                    <thead>
                        <tr>
                            <th className="th-sm">№</th>

                            <th className="th-sm">Name</th>
                            <th className="th-sm">Brand</th>
                            <th className="th-sm">Model</th>
                            <th className="th-sm"> Action </th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            assets.map(
                                function (item) {
                                    return <tr key={item.id}>
                                        {/* <tr> */}
                                        <th className="th-sm">{count++}</th>
                                        <th className="th-sm">Asset Name</th>
                                        <th className="th-sm">Asset Brand</th>
                                        <th className="th-sm">Asset Model</th>
                                        <th className="th-sm">
                                            {/* <Link to={`/item-delete/${item.id}`} className="btn btn-primary"> Delete Item </Link> */}
                                            <Link onClick={(id) => { deleteAsset(id) }} className="btn btn-primary"> Delete Asset </Link>
                                        </th>
                                    </tr>
                                }
                            )
                        }
                    </tbody>
                </table>

                <Modal show={show} size='lg' onHide={modalHide} centered>
                    <Modal.Header closeButton>
                        <Modal.Title center>
                            Add New Item
                        </Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <div className='container-fluid'>
                            <form className='row' method='POST' enctype="multipart/form-data">
                                {/* <form className='row' > */}
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Name </label>
                                    <input type='text'
                                        placeholder='Item Name'
                                        className='form-control'
                                        // value={itemname}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssettype(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Brand </label>
                                    <input type='text'
                                        placeholder='Item Brand'
                                        className='form-control'
                                        // value={itembrand} 
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssetBrand(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Model </label>
                                    <input type='text'
                                        placeholder='Item Model'
                                        className='form-control'
                                        // value={itemmodel}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssetModel(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>

                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file1}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage1(URL.createObjectURL(e.target.files[0]))
                                                setImage1(e.target.files[0])
                                            }
                                        }}
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file2}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage2(URL.createObjectURL(e.target.files[0]))
                                                setImage2(e.target.files[0])
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file3}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage3(URL.createObjectURL(e.target.files[0]))
                                                setImage3(e.target.files[0])
                                            }
                                        }}
                                    />
                                </div>
                            </form>
                        </div>
                        <div className='row mt-4'>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage1} />
                            </div>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage2} />
                            </div>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage3} />
                            </div>
                        </div>
                    </Modal.Body>
                    <Modal.Footer center>
                        <Button center onClick={(e) => { saveAndClose(e) }} variant="primary">Add New Asset</Button>
                    </Modal.Footer>
                </Modal>

            </div>
        </>
    )
}

export default AssetsComponenet

As you can see that in the form there is encription type - multipart: <form className='row' method='POST' enctype="multipart/form-data"> but any way I receive the error.

I use Axios for receive the data from backend, and I created the service for Items:

import axios from "axios";

const ASSETS_GOT_FROM_REST_API = "http://localhost:8082/api/assets";
const IMAGES_GOT_FROM_REST_API = "http://localhost:8082/api/assets/images";


class AssetsSetvice {
    getAllAssets() {
        return axios.get(ASSETS_GOT_FROM_REST_API);
    }
    addNewAsset(asset) {
        return axios.post(ASSETS_GOT_FROM_REST_API, asset);
    }
    deleteAsset(assetId) {
        return axios.delete(ASSETS_GOT_FROM_REST_API + "/" + assetId);
    }

    getAllImages() {
        return axios.get(IMAGES_GOT_FROM_REST_API);
    }
    addNewImages(images) {
        return axios.addNewImage(IMAGES_GOT_FROM_REST_API, images);
    }

}

export default new AssetsSetvice();

when I try to console log the item, which I try to add, I receive :

assetBrand : "fgdfgerg"
assetModel : "gvcvbdxbt"
assettype : "rterte"
file1 : File {name: 'lenovo-3.jpg', lastModified: 1684826589057, lastModifiedDate: Tue May 24 2023 10:23:09 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 4437, …}
file2 : File {name: 'm700-1.jfif', lastModified: 1684826441152, lastModifiedDate: Tue May 24 2023 10:20:41 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 3221, …}
file3 : File {name: 'm700-1.jfif', lastModified: 1684826441152, lastModifiedDate: Tue May 23 2023 10:20:41 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 3221, …}

seams that I am able to send the post request to server, but server refuse it. And I tried by the different ways.

What I do wrong? How can I solve tis issue? Can it be related with ManyToOne or OneToMany which were configured in Entity calsses? Because I dont see any other problems.... seams that something blocking in backend side...


Solution

  • I think the problem is in your controller:

    @RequestBody means to parse JSON data into map or java beans and only support content type is "application/json;charset=UTF-8"

    Maybe you can try with @ModelAttribute:

    @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
        public ResponseEntity<Object> saveAsset(@ModelAttribute Assets assets,
                                                @RequestParam MultipartFile file1,
                                                @RequestParam MultipartFile file2,
                                                @RequestParam MultipartFile file3)  throws IOException {