How to receive multipart request in Spring App - java

I've seen many sources and also few questions on SO but didn't find solution.
I want to send to my Spring app POST/PUT-requests that contain JSON-object Car and attached file.
For the moment I have a CarController which correctly works with JSON-objects
#PutMapping("/{id}/update")
public void updateCar(#PathVariable(value = "id") Long carId, #Validated #RequestBody Car car) throws ResourceNotFoundException {
// I can work with received car
}
I also have a FileController which correctly works with file
#PostMapping("/upload")
public void uploadFiles(#RequestParam("file") MultipartFile file) throws IOException {
// I can work with received file
}
But how should my method look like to be able to work with both car and file? This code doesn't provide me any of car or file.
#PutMapping("/{id}/update")
public void updateCar(#PathVariable(value = "id") Long carId, #Validated #RequestBody Car car, #RequestParam("file") MultipartFile file) throws ResourceNotFoundException, IOException {
// can not work neither with car nor with file
}
Separate controllers work well during test from Postman. But when I try third code I got these results:

You can use consumes = { MediaType.MULTIPART_FORM_DATA_VALUE } field of #RequestMapping annotation and #RequestPart annotation for method parameters:
ResponseEntity<> foo(#RequestPart ParType value, #RequestPart MultipartFile anotherChoice) {
...

Yes, I agree with Vladimir; multipart/form-data, #RequestParts instead of body & param:
#PutMapping(value = "/{id}/update", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public void updateCar(#PathVariable(value = "id") Long carId,
#RequestPart("car") Car car,
#RequestPart("file") MultipartFile file) {
...
Then in Postman:
use Body>form-data
when issues:
display Content-Type column.
set Content-Type per part.

There is nothing wrong with your code and it could work as it is.
You could eventually improve its readability by using #RequestPart instead of #RequestParam and #RequestBody when it's a multipart request.
You can find more details about multipart requests in this article https://www.baeldung.com/sprint-boot-multipart-requests
Most importantly, to make it work/ or to test in the correct way:
When using postman for multipart requests, you have to define the content type of each RequestPart.
It's a hidden column in the form-data screen, that you can show as follows:
Check the box "Content-Type" and the new column will appear:
And finally, define the content type of each part.

Related

File upload problem. In what form to send data to API

Prompt, I have a problem uploading files to the database. I need to implement a controller that accepts a number of files from 1 to n with additional parameters.
For example:
public class FilesDTO{
private String name;
private String type;
private MultipartFile file;
get / get
}
further implement the controller, for example:
#RequestMapping(value = "file/{id}", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE,
MediaType.MULTIPART_FORM_DATA_VALUE })
public ResponseEntity<?> addFile(#PathVariable Long id, #ModelAttribute List<FilesDTO> filesRequestList) throws IOException {
// log..
}
how to send a request correctly and in what form to this controller?
If you use postman.
maybe I misunderstood how to implement the controller, tell me how best to do
maybe this should be done not through dto
Use array of multipartfile (you can try to use list instead) and send files by form-data
#RequestMapping(value = "file/{id}", method = RequestMethod.POST)
public ResponseEntity<?> addFile(#PathVariable Long id, #RequestBody MultipartFile[] filesRequestList) throws IOException {
// log..
}

where to put code for swagger generated server code

I am just starting with swagger to generate a java Spring API.
The generation itself poses no problem. I used the PetStore yaml to generate the SpringBoot serverside code without a problem.
After i generate the code however I cannot find a good tutorial/way explaining to me where to best put the custom code i want to write. Writing it directly into the generated code does not seem like a good idea, since if i ever regenerate because of changes in the definitions you don't want to override the code used.
You get stubs like:
#Controller
public class PetApiController implements PetApi {
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
// do some magic!
return new ResponseEntity<ModelApiResponse>(HttpStatus.OK);
}
...
}
Now is there a way to fill the "do some magic!" part during/after generation with i.e. a call to a service, so that i can use this as sort of entry point. e.g. autowiring a service which has the same methods but can be provided by me, so that i can keep my implementations separate from the generated code.
#Controller
public class PetApiController implements PetApi {
...
#Autowired
PetApiService petApiService;
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
return petApiService.uploadFile(file);
}
...
}
Thanks
There is no easy way of regenerating new code using Swagger and stop it from overwriting any code you want to make. Although there is a way of mitigate all the copy-pasting if you want to change the api. Instead of writing all the code on the "do some magic" comment try to put in on a method in an outside class, so in the case you need to regenerate the code with swagger, you would only need to copy a single line, I'll give you an example:
#Controller
public class PetApiController implements PetApi {
#Autowired
GenerateResponse generateResponse;
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
return generateResponse.uploadFile(petId, additionalMetadata, file);
}
...
}
And in your outside class that I called "GenerateResponse":
#Component
public class GenerateResponse{
#Autowired
PetApiService petApiService;
public ResponseEntity<ModelApiResponse> uploadFile(Long petId, String additionalMetadata, MultipartFile file){
return petApiService.uploadFile(file);
}
}
So you just have to copy the "return GenerateResponse.uploadFile(petId, additionalMetadata, file);" line and only create the autowired GenerateResponse once every time you change it.

Content type 'multipart/form-data;boundary=----...;charset=UTF-8' not supported

I want to send an object to the controller that has several lists with files and several fields with plain text.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I am sending data using postman. However, he throws me away
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundarywY7ByvgonAjDoaCT;charset=UTF-8' not supported
What should I set the Content-type for this controller so that I can send an object that has fields of plain text and lists with files?
If I set the header in the header
Content-type: multipart/form-data; charset=utf-8
it throws me in the console
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
As said dknight #RequestBody means use of JSON or XML data with maps your DTO bean.
In case of MultipartFile you can't use JSON data so you can't use #RequestBody.
Try with #ModelAttribute annotation.
Working sample :
#PostMapping("/promoters")
#Timed
public ResponseEntity<PromoterDTO> createPromoter(#ModelAttribute PromoterDTO promoterDTO) throws URISyntaxException { ... }
With PromoterDTO like this :
public class PromoterDTO implements Serializable {
private Long id;
private String name;
private String address;
private MultipartFile logo;
}
In Postman, you need to set the body to be of type raw, and from the drop down you can select JSON, I had a similar issue, this fixed my issue.
Instead of #RequestBody, use #RequestParam!
use #ModelAttribute instead of #ResponseBody as this takes up data in key value pairs and the later is used for an object like, json.
While hitting the api simply pass the multipart type and json key value pairs of the object. It works fine!
stack overflow question on this
Instead of #RequestBody, use #ModelAttribute like,
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#ModelAttribute #Valid final ContributionNew<Photo> contribution
) {
import org.springframework.web.bind.annotation.ModelAttribute;
Use #ModelAttribute instead of #RequestBody. It worked for me.
produces = { "application/json" } has to written in the controller instead of consumes = { "application/json" }
Hi Folks simply change : #RequestBody to #ModelAttribute
public ResponseEntity<DTO> exemple(#ModelAttribute Dto dto) throws TechnicalException
Happy coding.
Here's a full code sample written in Kotlin using Spring Boot 2.1.7
Example uses a ProfileRepository that would be something you implement of course.
Kotlin is nice, because the data class implements serializable already.
Take note, that you have to use var not val for the model objects properties otherwise the field values will be null in the log message.
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
#RestController
class ExampleController(private val myProfileRepository: ProfileRepository) {
#PostMapping("/api/uploadFile")
fun createProvider(#ModelAttribute request: CreateProfileRequest): Provider {
println("received create request with photo: ${request.photo} for the following person ${request.name}")
return myProfileRepository.save(Provider(name = request.name!!))
}
}
data class CreateProfileRequest(
var name: String? = null,
var photo: MultipartFile? = null
)

Can I create multiple HTTP POST methods with same request mapping with different requestbody

Can I create multiple HTTP POST methods with same request mapping with different requestbody
#RequestMapping("api/v1/")
#RequestMapping(value = "test" ,method = RequestMethod.POST)
public RObjet create(#RequestBody RBody rbody)
{
// do some process...
}
#RequestMapping("api/v1/")
#RequestMapping(value = "test" ,method = RequestMethod.POST)
public RObjet2 create2(#RequestBody RBody2 rbody)
{
// do something.
}
Is this possible.? How do we handle this in spring boot.
Yes, you can use POST Http Method for the same end point URI with different request body and also you could get different responses. One way to achieve this, is mapping requests using end point URI + Headers
e.g.
#RestController
#RequestMapping("/api/bills")
public class BillingController {
#RequestMapping(method = RequestMethod.POST, headers = "action=add-bill")
public BillId addBill(#Valid #RequestBody BillingData data) {
//Some code
}
#RequestMapping(method = RequestMethod.POST, headers = "action=delete-bill-by-id")
#ResponseStatus(code = HttpStatus.NO_CONTENT)
public void removeBill(#Valid #RequestBody BillId identifier) {
//Some code here to remove bill
}
}
In this case, both class methods in BillingController are mapped to the same HTTP Method (POST) and URI (/api/bills). The header action drives what class method in BillingController is going to be invoked once you point your post request to /api/bills
How to hit BillingController.addBill?
NOTE: I know that good REST API design dictates that if I want to delete records I should use DELETE method, however this sample was created only as reference to show how to use same URI/Method to handle 2 different end points.
You have to option for this.
it is possible with consumes field. You can use different consuming types.
You can user params field if you have in url.
#RequestMapping(value="/path", params="id")
public String test1(#RequestBody RBody body) {}
#RequestMapping(value="/path", params="name")
public String test2(#RequestBody RBody body) {}

Multipart File upload using Springfox and Swagger-ui

I'm using Spring MVC as a rest controller and I've integrated Swagger-ui with my controller using Springfox. I'd like to have a method that is able to upload a file via the Swagger-ui interface. I only need two parameters, a long acting for an object id and the file to be uploaded.
#RestController
public class controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestParam MultipartFile file){
//do some stuff
}
}
I've tried almost everything and I can't get a file upload button to appear. However, if I do:
#RestController
public class Controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestPart File file){
//do some stuff
}
}
The file upload button appears, but it always throws http code 415 when trying to upload a file. Besides, I need the input to be a MultipartFile, not a regular File. Even if I use the #RequestPart annotation with Multipart File, the choose file to upload button does not appear. How can I get this to work???? Even:
#RestController
public class Controller{
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestPart String metaData,
#RequestPart MultipartFile file){
//do some stuff
}
}
Won't work. If someone could give a walkthrough of how to get this button to appear for MultipartFile? I'd be forever grateful.
I think you are missing the consumes attribute of the #RequestMapping in your second snippet. See the following example
#RequestMapping(
path = "/upload",
method = RequestMethod.POST,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> handleUpload(
#RequestPart("file") MultipartFile file,
#RequestParam("someId") Long someId,
#RequestParam("someOtherId") Long someOtherId) {
return new ResponseEntity<>();
}
Use
#RequestPart(required = true) MultipartFile file
And use the version number 2.1.0 or latest, there is a bug with previous versions.
https://github.com/springfox/springfox/issues/786
In my situation, there were two things I needed to do
My MultipartFile request param had to be named 'file', otherwise, swagger-ui wouldn't display the file upload input control
#RequestParam("file") MultipartFile file
I had to register the following bean
#Bean(name = "multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver(){
return new CommonsMultipartResolver();
}
Try using #RequestPart for MultipartFile instead of #RequestParam
#RestController
public class controller {
#RequestMapping(value="/upload", method=RequestMethod.POST)
public void uploadFile(#RequestParam Long id,
#RequestPart MultipartFile file) {
//do some stuff
}
}
Two things...
Value of consumes should should be "multipart/form-data". consumes="multipart/form-data"
#RequestPart("file") #ApiParam(value="File", required=true) MultipartFile file

Categories

Resources