How to pass MultipartFile in requestBody for a spring boot rest controller - spring

So I have seen examples where a MultiPartFile type is passed in #RequestParam and not in #RequestBody. That seems to be a very usual way people suggest to consume a file content in a #RestController something like this
public ResponseEntity<String> submitFile(#RequestParam(value="file") MultipartFile file)
I am wondering how is it a good practice as the file data gets passed in the url. Why not pass it in #RequestBody instead?
So I changed the above code to something like this
public ResponseEntity<String> submitFile(#RequestBody MyCustomObj myObj)
myCustomObj is a pojo with just one field named file of type MultipartFile
The problem is that I only have swagger and postman to test it and when I use the #RequestBody approach, none of these would give me an option to upload a file as they would in case of passing MultipartFile in RequestParam.
Can someone please throw some more light on this and tell me the right way to do this?

As an alternative and based on your comments I would recommend you take a look at the community project called Spring Content. This provides a resource abstraction over storage giving flexibility to where your content is stored and it injects the service and controller implementations for you so that you don't need to implement either yourself. Also, as you mentioned it might become important, Spring Content allows you to associate uploaded content with Spring Data entities too.
Adding it to your project would look something like this:
pom.xml (assuming maven. Spring boot starters also available)
<!-- Java API -->
<!-- just change this depdendency if you want to store somewhere else -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs</artifactId>
<version>0.8.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.8.0</version>
</dependency>
StoreConfig.java
#Configuration
#EnableFilesystemStores
#Import(RestConfiguration.class)
public class StoreConfig {
#Bean
FileSystemResourceLoader fileSystemResourceLoader() throws IOException {
return new FileSystemResourceLoader(new File("/path/to/uploaded/files").getAbsolutePath());
}
}
FileStore.java
#StoreRestResource(path="files")
public interface FileStore extends Store<String> {
}
And that's it. The FileStore is essentially a generic Spring ResourceLoader. The spring-content-fs dependency will cause Spring Content to inject a filesystem-based implementation. The spring-content-rest dependency will cause Spring Content to also inject an implementation if an #Controller that forwards HTTP requests onto the methods of the FileStore service.
So you will now have a fully functional (POST, PUT, GET, DELETE) REST-based file service at /files that will use your FileStore to retrieve (and store) files in /path/to/uploaded/files on your server.
So:
curl --upload-file some-image.jpg /files/some-image.jpg
will upload some-image.jpg and store it in /path/to/uploaded/files on your server.
And:
curl /files/some-image.jpg
will retrieve it again.
HTH
The injected controller also supports video streaming too, in case that is useful.
Later on down the line if/when you want to associate content with a Spring Data entity all you would need to do is make your FileStore extend ContentStore instead of Store, type it to the Spring Data entity that you are associating with and add the Spring Content annotations to your entity, as follows:
//#StoreRestResource(path="files") <-- no longer required
public interface FileStore extends ContentStore<YourEntity, String> {
}
#Entity
public class YourEntity {
#Id
...
#ContentId
private String contentId;
#ContentLength
private String contentLen;
#MimeType
private String contentType;
}
And that's it. As you might expect your REST endpoints change so that you now address content using the same URI space as your Spring Data entity. So:
curl --upload-file some-image.jpg /yourEntities/{yourEntityId}
will upload some-image.jpg, store it in /path/to/uploaded/files on your server and associate it with the entity yourEntityId.
And:
curl /yourEntities/{yourEntityId}
will retrieve it again.
Multiple pieces of content can be associated by using conventional #OneToOne and #OneToMany associations and are reflected accordingly in the URI in a (hopefully) intuitive way.
HTH

#RequestParam maps to query parameters, form data, and parts in multipart requests and not only query parameters as mentioned the offical docs.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
Files are not supposed to be sent as the request body serialized in JSON.
What you should do instead is to use the content type "multipart/form-data" for your file uploads (as mentioned in the HTML 4 spec below) and in that case #RequestParam will be the appropriate annotation to use
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

Related

How to use spring-data-rest without href for relation

I'm migrating a legacy application from Spring-core 4 to Springboot 2.5.2.
The application is using spring-data-rest (SDR) alongside spring-data-mongodb to handle our entities.
The legacy code was overriding SDR configuration by extending the RepositoryRestMvcConfiguration and overriding the bean definition for persistentEntityJackson2Module to remove serializerModifier and deserializerModifier.
#EnableWebMvc
#EnableSpringDataWebSupport
#Configuration
class RepositoryConfiguration extends RepositoryRestMvcConfiguration {
...
...
#Bean
#Override
protected Module persistentEntityJackson2Module() {
// Remove existing Ser/DeserializerModifier because Spring data rest expect linked resources to be in href form. Our platform is not tailored for it yet
return ConverterHelper.configureSimpleModule((SimpleModule) super.persistentEntityJackson2Module())
.setDeserializerModifier(null)
.setSerializerModifier(null);
}
It was to avoid having to process DBRef as href link when posting entities, we pass the plain POJO instead of the href and we persist it manually before the entity.
Following the migration, there is no way to set the same overrided configuration but to avoid altering all our processes of creation we would like to keep passing the POJO even for DbRef.
I will add an exemple of what was working before :
We have the entity we want to persist :
public class EntityWithDbRefRelation {
....
#Valid
#CreateOnTheFly // Custom annotation to create the dbrefEntity before persisting the current entity
#DBRef
private MyDbRefEntity myDbRefEntity;
}
the DbRefEntity
public class MyDbRefEntity {
...
private String name;
}
and the JSON Post request we are doing:
POST base-api/entityWithDbRefRelations
{
...
"myDbRefEntity": {
"name": "My own dbRef entity"
}
}
In our database this request create our myDbRefEntity and then create the target entityWithDbRefRelation with a dbRef linked to the other entity.
Following the migration, the DBRef is never created because when deserializing the JSON into a PersistingEntity, the myDbRefEntity is ignored because it's expecting an href instead of a complex object.
I see 3 solutions :
Modify all our process to first create the DBRef through one request then create our entity with the link to the dbRef
Very costly as we have a lot of services creating entities through this backend
Compliant with SDR
Define our own rest mvc controllers to do operations, to ignore the SDR mapping machanism
Add AOP into the RepositoryRestMvcConfiguration around the persistentEntityJackson2Module to set le serializerModifier and deserializedModifier to null
I really prefer to avoid this solution as Springboot must have remove a way to configure it on purpose and it could break when migrating on newer version
Does anyone know a way to continue considering the property as a complex object instead of an href link except from my 3 previous points ?
Tell me if you need more information and thanks in advance for your help!

Multiple default content type in spring boot

We are migrating monolithic netty web application into multiple spring boot microservices. Currently the consumers are not passing Content-Type header in the request.
To support that i have added below code in my application
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
But the problem is i have endpoints with application/json as content type.
Is it possible to have default content type based on url's or is it possible to add headers in the request using some filter or interceptor.
If you add mediaType to your configuration it will enable content negotiation for whichever media types you want.
Spring checks three things, in this order
Add an extension to your request, e.g. /endpoint becomes /endpoint.json if you want JSON
Add the format parameter, e.g. /endpoint becomes /endpoint?format=json
Add the requisite Accept header, e.g. Accept=application/json (unless you configure the ContentNegotiationConfigurer to ignore the Accept header.
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
Configured in this manner, your endpoints will marshal XML unless overridden by one of the means described above.
If you cannot make changes to your clients to ask that they request the format they expect, you can override the behavior of a specific endpoint by adding produces to the mapping, like this
#GetMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
or
#RequestMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
This answer assumes that you've imported the correct dependencies into your project, you'll need the jackson XML mapper. Also, assuming you leave the content negotiation configuration in place, I would highly recommend that on your methods that you specify produces, that you include produces = {"application/xml", "application/json"} just in case your clients start following the proper specs and send an Accept header. Also, note that you'll need to make sure your objects are annotated with at a minimum, #XmlRootElement
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Why do we need jackson databind?

I am new in Spring MVC. My question is, why do we need jackson databind? Because We can receive the Request Params by #ModelAttribute and requests through http PUT or POST by #RequestBody. I can't find a reason why we need jackson databind to convert json/xml to POJO or vice versa.
Thanks.
Why do we need jackson databind?
Because representing structured data is much easier using XML (or JSON) than using simple name-value pairs.
Because it is more convenient to send and receive JSON from the client side when you are doing AJAX.
Because once you have to deal with sending and receiving JSON or XML in the server side Java app, it is more convenient to deal with structured data as POJOs.
None of the above points mean you have to use a binding. There are other ways of dealing with each of the above. But many Java developers think that data bindings the better way to go: more efficient in terms of developer time, and more reliable. Especially if you are implementing services with a complex APIs. That's why they are popular.
And as other answers/comments point out, if you are using #RequestBody, then that is using a binding library under the hood to give you the POJOs. In the case of Spring, it is Jackson that is being used.
By default, when an endpoint expects a JSON document as input and a given controller method argument is annotated with #RequestBody, Spring will use Jackson databind features to map the incoming JSON document to a Java object. You don't need to use the Jackson's ObjectMapper directly, as Spring does it for you.
For example purposes, consider the following HTTP request to create a comment:
POST /comments HTTP/1.1
Host: example.org
Content-Type: application/json
{
"content": "Lorem ipsum"
}
And the following class which represents a comment:
#Data
public class Comment {
private String content;
}
A #RestController to handle such request would be like:
#RestController
#RequestMapping("/comments")
public class CommentController {
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Foo> createComment(#RequestBody Comment comment) {
// By default, Spring will rely on Jackson databind to map the incoming
// JSON document to the comment argument annotated with #RequestBody
...
}
}
If you are interested in the Spring component that maps the incoming JSON document to a Java object, have a look at the MappingJackson2HttpMessageConverter class:
Implementation of HttpMessageConverter that can read and write JSON using Jackson 2.x's ObjectMapper.
This converter can be used to bind to typed beans, or untyped HashMap instances.
By default, this converter supports application/json and application/*+json with UTF-8 character set. [...]
If you are creating a HTTP API and exposing resources that can be manipulated with JSON representations, it's unlikely you'll use #ModelAtribute. Such annotation is particularly useful when you are dealing with web views.
When you get some request in some data types like json/xml, the Spring MVC platform will try to deserialize this request attributes in some model object of your project.
But the platform itself don't provide a des-serialize implementation out of the box. So it will try to use some des-serializer provider in the classpath like jackson, jersey, gson, etc.
As you said - is possible to use #ModelAttribute - but this annotation is a better option to a request from a form view in the front-end. In cases rest json/xml requests, the #ModelAttribute won't be able to convert correctly the received data to a business class of your program.

Is there a way to link JAX-RS resource to another resource like in Spring HATEOAS?

In Spring we've got #ExposesResourceFor annotation which can link our resource with other resources. Thanks to this our Value objects (representations) can know nothing of the actual resources.
Is there a way to do it in JAX-RS? I'm using Dropwizard with Jersey and Jackson and all I see is #InjectLinks annotation which I can use in a value object like this:
public class UserGroup {
#JsonProperty
public String name;
#InjectLinks(GroupsResource.class)
public URI myResource;
public UserGroup(String name){
this.name = name;
}
}
But unfortunatelly my Value Objects should know nothing about Resources, so I'm asking can I do such linking on the level of resources - link in spring-hateoas in controllers, as mentioned above.
With #InjectLinks, you don't have to declare the links in your model class. You can create a "wrapper" representation class, as shown in declarative-linking from the Jersey examples (though this solution is not really on the resource class level as you wish).
Another possible solution (rather than declarative linking) is to use the JAX-RS 2.0 Link class, and do the linking programmatically (with no ties to the Jersey implementation/annotations). You can either add the links to your response headers, as see here, or add Links to you model classes, as seen here (or use the wrapper class for this also, so as to not to invade your model classes)
Some Resources
Declarative Hyperlinking
Using Link for headers

Jersey Jackson and Spring Service

We are using Jersey, Jackson and Spring service (#service) annotation to expose some REST based Web-Services. Request and response data are exchanged in JSON Format.
Below is the Service Request Object content:
public class ServiceRequest{
private RequestHeader requestHeader;
private List<BaseEntity> requestData;
}
All specific entities will extend from BaseEntity class. For example :
public class User extends BaseEntity{
private String userName;
}
For all service operation we accept only ServiceRequest Object by passing list of request data objects. Now when we try to call these operation from REST Client, these are failing with
userName is not found as part of BaseEntity.
This is because while converting, Jersey/Jackson tries to autodetect the incoming field names with the specified object's property.
I want to know is there any way, I can handle this in an intelligent way. We do not have an option to change the signature of the Service. Really appreciate your help on this.
The below link has the answer for my question. Thank you for the support.
Polymorphism in jackson annotations: #JsonTypeInfo usage
Did you tried to use generics? You can declare concrete class type at Resource's to inform Jackson to map to specific type

Resources