Josh M.
Josh M.

Reputation: 27781

Use RestTemplate with object as data and application/x-www-form-urlencoded content type?

I need to post an object (e.g. not a MultiValueMap) via a RestTemplate with the content type application/x-www-form-urlencoded. When I try to do so ...

HttpHeaders headers = new HttpHeaders();
HttpEntity request;

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED)

// data is some generic type
request = new HttpEntity<>(data, headers);

// clazz is the Class<T> being returned
restTemplate.exchange(url, method, request, clazz)

... I get the following error:

org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.whatever.MyRequestPayload] and content type [application/x-www-form-urlencoded]

Here is what I see within restTemplate.getMessageConverters():

message converters

Why don't I want to provide a MultiValueMap? Two reasons:

  1. this is general purpose code which is used to send requests to multiple endpoints, so adding an overload specifically for x-www-form-urlencoded will only complicate things
  2. it doesn't seem like I should have to -- I just don't know which HttpMessageConverter needs to be used to support converting objects to a x-www-form-urlencoded string

Upvotes: 33

Views: 46971

Answers (6)

puppylpg
puppylpg

Reputation: 1220

Reason: there is no converter can convert your java object into request body in x-www-form-urlencoded format.

Solution1: create this kind of converter, as what @Josh M. posts.

Solution2: convert your java object into MultiValueMap, and there is already a converter named FormHttpMessageConverter in spring boot which will convert MultiValueMap into request body in x-www-form-urlencoded format automatically.

So in solution2, all you need is to convert your java object into MultiValueMap:

        MultiValueMap<String, String> bodyPair = new LinkedMultiValueMap();
        bodyPair.add(K1, V1);
        bodyPair.add(K2, V2);
        bodyPair.add(K3, V3);
        ...

K1, V1, K2, V2, ..., means the field names and corresponding values in your java object. All the fields you declared in your java class need to be added. If there are too many fields, consider using Java Reflection.

Upvotes: 23

anand kadu
anand kadu

Reputation: 76

You can use FormHttpMessageConverter for content-type "application/x-www-form-urlencoded" as below and send data in RestTemplate post method for body as MultiValueMap<String, String>

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate" lazy-init="true">
    <constructor-arg name="requestFactory" ref="bufferingClientHttpRequestFactory" />
    <property name="errorHandler" ref="responeErrorHandler" />
    <property name="messageConverters">
    <list>
        <bean class="org.springframework.http.converter.FormHttpMessageConverter" />
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    </list>
</property>
</bean>

Java Config Version

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();          
    restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
    restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    return restTemplate 
}

Upvotes: -2

yv84_
yv84_

Reputation: 131

You can try use https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html

RestTemplate template = new RestTemplate(...);
template.getMessageConverters().add(new org.springframework.http.converter.FormHttpMessageConverter.FormHttpMessageConverter());

Upvotes: 5

user1467300
user1467300

Reputation: 58

If you are using WebMvcConfigurerAdapter then you might get such errors. Also WebMvcConfigurerAdapter is deprecated from Spring 5.x.x. Try using the WebMvcConfigurer. Once done you will have to override:

@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}

Upvotes: 0

Josh M.
Josh M.

Reputation: 27781

I ended up having to write a custom HTTP message converter which takes any object and writes it out as www-form-urlencoded content to the request body:

Usage

RestTemplate template = new RestTemplate(...);

template.getMessageConverters().add(new ObjectToUrlEncodedConverter(mapper));

ObjectToUrlEncodedConverter

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;

public class ObjectToUrlEncodedConverter implements HttpMessageConverter
{
    private static final String Encoding = "UTF-8";

    private final ObjectMapper mapper;

    public ObjectToUrlEncodedConverter(ObjectMapper mapper)
    {
        this.mapper = mapper;
    }

    @Override
    public boolean canRead(Class clazz, MediaType mediaType)
    {
        return false;
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType)
    {
        return getSupportedMediaTypes().contains(mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes()
    {
        return Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED);
    }

    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException
    {
        throw new NotImplementedException();
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException
    {
        if (o != null)
        {
            String body = mapper
                .convertValue(o, UrlEncodedWriter.class)
                .toString();

            try
            {
                outputMessage.getBody().write(body.getBytes(Encoding));
            }
            catch (IOException e)
            {
                // if UTF-8 is not supporter then I give up
            }
        }
    }

    private static class UrlEncodedWriter
    {
        private final StringBuilder out = new StringBuilder();

        @JsonAnySetter
        public void write(String name, Object property) throws UnsupportedEncodingException
        {
            if (out.length() > 0)
            {
                out.append("&");
            }

            out
                .append(URLEncoder.encode(name, Encoding))
                .append("=");

            if (property != null)
            {
                out.append(URLEncoder.encode(property.toString(), Encoding));
            }
        }

        @Override
        public String toString()
        {
            return out.toString();
        }
    }
}

Upvotes: 28

Mohamed Habib
Mohamed Habib

Reputation: 903

Have you tried something like adding MappingJackson2HttpMessageConverter to the RestTemplate

restTemplate.getMessageConverters().add(getMappingJackson2HttpMessageConverter());

public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED));
    return mappingJackson2HttpMessageConverter;
}

Upvotes: 6

Related Questions