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...
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 {