I get the following error when trying to save a vehicle entity attached with an image:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of org.springframework.web.multipart.MultipartFile (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Here is my controller code and the request body class I made:
#PostMapping("/save")
public ResponseEntity<Vehiculo> postVehiculo(#RequestBody VehiculoSaveBody json) throws IOException{
Vehiculo vehiculo = json.getVehiculo();
Version_Vehiculo version = json.getVersion();
MultipartFile file = json.getFile();
System.out.printf(file.getResource().getFilename());
return vehiculoService.postVehiculo(vehiculo, version, file);
}
#Data
public class VehiculoSaveBody{
Vehiculo vehiculo;
Version_Vehiculo version;
MultipartFile file;
}
I've seen in every tutorial that everyone receives de file as a RequestParam and specifies the name, something like #RequestParam("file") MultipartFile file.
Is the error caused by the way I receive the Json as a RequestBody?
try #MultipartForm of org.jboss.resteasy.annotations.providers.multipart.MultipartForm instead of #RequestBody
Related
I created a spring RestController to process fileUpload from a react js UI.
To be able to use custom validations by #Validated annotation, had to wrap the MultipartFile into a class, I named it as UploadedFile. Created my Post Handler method with argument as #ModelAttribute.
Everything works fine.
validate(target, error) method in my custom validator is called and inside POST handler method, UploadedFile object has the multipart file containing the uploaded document..
Here is a perfectly working code
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #ModelAttribute UploadedFile file) {
}
#Getter
#Setter
public class UploadedFile {
MultipartFile file;
}
// one CustomValidator class and webDataBinder.addValidators(customValidator) in controller
multipart.enabled=true //in application.properties
So far everything works as expected, Problem arise when
Someone asked me that, #ModelAttribute is a spring MVC construct, as this is a microservice, which in future, apart from my React UI, will cater to other api requests too, so I should use #RequestParam instead of #ModelAttribute.
So I changed #ModelAttribute to #RequestParam as follows
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") UploadedFile file)
But now I get below exception
Failed to convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile';
nested exception is java.lang.IllegalStateException:
Cannot convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile':
no matching editors or conversion strategy found
If, for #RequestParam, I change type as MultipartFile instead of UploadedFile then my posthandler gets called but the custom Validator doesn't gets called by spring before handler call,
I tried to use instanceOf MultiPartFile in validator supports method by still no avail.
#PostMapping(value="/file" )
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") MultipartFile file)
I've the work around to explicitly call the custom validator method by code as first line in POST handler, so I dont need a solution MY QUESTION IS
How come, without adding any custom #Bean or any extra external dependencies everything works fine with #ModelAttribute, but merely changing the annotation #RequestParam doesn't work.
Working with a vanilla #RestController in Spring.Boot, how can you use the default databinding and properly bind to nested Optional attributes in a data object?
In terms of code, this is the data class (using lombok to reduce the boilerplate)
#Data
public class SomeData {
Optional<String> name;
}
which is used as a request parameter in a GET route, like this
#GetMapping("/something")
public void getSomething(SomeData data) {
...
}
The problem: if no corresponding parameter is given in the request, then data.name is null instead of Optional.empty.
What I found so far:
* Optional is handled properly when you just use it directly, like getSomething(Optional<String> name)
* Additional annotations like #RequestParam or #Valid don't affect this behavior
I was not experiencing this problem early in development but just noticed that this was happening when debugging another problem. This happens on all REST endpoints, but below is an example:
#RestController
#RequestMapping("/editlisting")
public class EditParkingSpaceListingController {
#Autowired
ParkingSpaceRepository parkingSpaceRepository;
#Autowired
ParkingSpaceListingRepository parkingSpaceListingRepository;
#RequestMapping(method = RequestMethod.PUT)
public ResponseEntity<String> editParking(#RequestBody ParkingSpaceListingClient pslc, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<String>("", HttpStatus.BAD_REQUEST);
}
// Code to save pslc data to database.
Now, if I send an HTTP request with the body as
{ }
I get a 200 response and when I check MongoDB, there is a new empty document in the collection. If I send an empty body with no brackets, as expected it will return 400. If I send a body with random garbage data that does not exist in the POJO, BindingResult does not seem to pick up the error and a new blank document is still created.
You need to follow the below steps for the input document validations:
(1) Add the javax.validation package constraints (like #NotNull, #Size, etc..) to your ParkingSpaceListingClient bean class.
(2) Add #Validated annotation to your controller method, to capture the validation errors into BindingResult object.
You can look here for more details on Input Validations.
I'm trying to use the #InitBind annotation to map only certain fields on the object in the request body.
I have a spring controller defined in this way:
#RequestMapping(value = "addAddress", method = RequestMethod.POST)
public Object addAddressToPerson(
HttpServletRequest request,
HttpServletResponse res,
#RequestParam(value = "name", required = false) String name,
#RequestParam(value = "surname", required = false) String surname,
#RequestBody personDTO personJson,BindingResult result) {
The client request will be a a json representing a personDTO, but I don't want that field except the address to be mapped in the object for security reasons.
The input will be something like:
{ "address":"123 Street","........}
The personDTO contains many fields, and since spring map all of them directly in a DTO, that can be a problem.
I've seen that a solution is to use a Binder to declase the allowed or Disallowed field, but if I check the personDTO inside the controller, other fields are populate (for example if pass "id":"1234").
Any Hints?
The binder code is the following:
#InitBinder("orderJson")
protected void orderJsonBinder(WebDataBinder binder){
binder.setAllowedFields(new String[]{"address"});
}
Am I missing something?
Best Regards,
Luca.
But you are not binding request parameters to a model attribute bean, you are just asking spring to use an appropriate MessageConverter to convert the request body. As you say it is Json, you will use a MappingJackson2HttpMessageConverter (or MappingJacksonHttpMessageConverter with Jackson 1.x). The Spring Reference Manual says for this converter :
[This is an] HttpMessageConverter implementation that can read and write JSON using Jackson's ObjectMapper. JSON mapping can be customized as needed through the use of Jackson's provided annotations. When further control is needed, a custom ObjectMapper can be injected through the ObjectMapper property for cases where custom JSON serializers/deserializers need to be provided for specific types. By default this converter supports (application/json).
#InitBinder can only configure binding of #ModelAttribute annotated parameters. It is useless here. If Jackson annotations are not enough, you will have to use a custom object mapper.
And I am surprised that you can use a BindingResult after a #RequestBody parameter, because the documentation says that it should follow a #ModelAttribute one.
I'd like to use the MvcUriComponentsBuilder::fromMethodCall method to build URLs from my controllers. I normally have a String return type (which returns the view name) and a Model instance as method parameter in my controller methods like:
#Controller
public class MyController {
#RequestMapping("/foo")
public String foo(Model uiModel) {
uiModel.addAttribute("pi", 3.1415);
return "fooView";
}
}
I try to generate a URL e.g. like:
String url = MvcUriComponentsBuilder.fromMethodCall(on(MyController.class).foo(null)).build().toUriString();
This leads to this exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class java.lang.String
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-4.1.4.RELEASE.jar:4.1.4.RELEASE]
This happens because the String return type wants to get proxied, but can't as a final class.
What's a way to overcome this? I'd like to keep the String as a return type and get the Model as input from a parameter in my controller methods because IMHO it's way easier than handling a ModelAndView instance in every controller method.
fromMethodCall uses CGLIB proxy in the process which is why you run into the issue. This article details why.https://github.com/spring-projects/spring-hateoas/issues/155. Try using fromMethodName if you want to maintain the String return types.
MvcUriComponentsBuilder.fromMethodName(MyController.class, "foo", new Object()).build();
Consider changing the signature of the method to return Spring's ModelAndView vs. returning String. For example:
#Controller
public class MyController {
#RequestMapping("/foo")
public ModelAndView foo() {
return new ModelAndView("fooView", "pi", 3.1415);
}
}
With this refactored signature, the corresponding fromMethodCall invocation would look like this:
UriComponents uri = fromMethodCall(on(MyController.class).foo()).build();