Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
Ask Question
I'm writing code to
POST
data to a third party API using a
RestTemplate
. That API responds with the content-type
text;charset=UTF-8
, and Spring throws an
InvalidMediaTypeException
because that content type does not contain a
/
. Is it possible to indicate to Spring that a content-type of
text
should be treated the same as a content type of
text/plain
? If so, how do I accomplish this?
This is the code that's causing the problem. I can't show the
URL
, but I assume that doesn't really matter.
// Make the body of the request.
MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();
body.add("Customer Street", "a test street!");
// Make the headers of the request.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Arrays.asList(MediaType.TEXT_PLAIN));
// Create an HTTP entity.
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(body, headers);
// Get a rest template
RestTemplate rest = new RestTemplate();
// Post the data.
String resp = null;
try {
resp = rest.postForObject(URL, entity, String.class);
} catch (InvalidMediaTypeException e) {
e.printStackTrace();
return;
Related SO Questions
This question describes almost exactly the problem I'm having. The accepted answer to that question is, essentially, "See this other question," which I did; it's described below.
In this question (linked in the answer to the above), istibekesi asks about using a custom content-type of myXml
, and provides an example configuration that did not work. Brian Clozel's answer helped me understand some things about content-types that I didn't understand before, but I'm still confused on these points:
Brian says that the given configuration should register myXml
as a path extension / parameter for negotiating to application/xml
. My initial understanding was that Brian meant, "Requests with the accept header set to myXml
should be treated the same as requests with the accept header set to application/xml
." However, now I'm pretty sure Brian meant, "Requests ending in .myXml
or with the query parameter format=myXml
should be treated as application/xml
." Is my second interpretation correct? If so, why doesn't the given configuration force Spring to treat requests with the accept header set to myXml
as application/xml
?
Brian states that what istibekesi should probably do is register an HttpMessageConverter
and then register it with application/xml
and myXml
. I think I understand what Brian means by saying, "register an HttpMessageConverter
," however I can't figure out how to register a custom media type (such as myXml
) to use that HttpMessageConverter
.
Unfortunately, Brian's advice of "use a media type like application/vnd.foobar.v.1.0+xml
" isn't helpful to me, because I have no control over the content-type of the response that I'm sent. I have tried setting the accept headers of my request to text/plain
, but it didn't change the response.
Other Research
The stack trace of the exception that Spring is throwing is
org.springframework.http.InvalidMediaTypeException: Invalid mime type "text;charset=UTF-8": does not contain '/'
at org.springframework.http.MediaType.parseMediaType(MediaType.java:452)
at org.springframework.http.HttpHeaders.getContentType(HttpHeaders.java:745)
at org.springframework.web.client.HttpMessageConverterExtractor.getContentType(HttpMessageConverterExtractor.java:114)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:85)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:380)
at com.agileBTS.hellosignTest.App.main(App.java:47)
Caused by: org.springframework.util.InvalidMimeTypeException: Invalid mime type "text;charset=UTF-8": does not contain '/'
at org.springframework.util.MimeTypeUtils.parseMimeType(MimeTypeUtils.java:256)
at org.springframework.http.MediaType.parseMediaType(MediaType.java:449)
I went through the source code of every function on the stack trace to see if I could figure out what was going on, and I can see clearly that the parseMimeType
method (the first method to throw an exception) is pretty straightforward: if the mime-type does not contain a /
, it throws an exception. I don't understand how any code is going to get around this, unless I subclass MimeTypeUtils
and force Spring to use my subclass. Is that what's required? That seems very difficult.
Updates
In their answer, Sean Carroll suggested that I register the "text"
mime type using the line
c.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.parseMediaType("text")));
However, if you check out the source code for the #parseMediaType
method here at line 487, you'll see that #parseMediaType
hands off most of the work to the MimeTypeUtils#parseMimeType
method. Looking at that source code here starting at line 176, it's clear the #parseMimeType
will throw an IllegalMimeTypeException
at line 193, because "text"
does not contain a /
(in fact, this is the exact line of code that throws the IllegalMimeTypeException
in my application). What I need is a way around this.
After testing, I've determined that configuring a Content Negotiation Manager also does not work in my case. Based on this tutorial, I think it's clear that the XML configuration:
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</property>
</bean>
is equivalent to this Java configuration:
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON);
Taking a look at the ContentNegotiationConfigurer#mediaType
method documentation here, I saw the line, "Add a mapping from a key, extracted from a path extension or a query parameter..." (emphasis mine); I guess excluding "accept headers" from that quote was intentional.
This is how I interprets Brian's answer. I'm assuming you are using java configuration. To register an HttpMessageConverter you would do something like the following
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter();
// add support for "text" media type. This may require Charset of UTF-8
c.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.parseMediaType("text")));
converters.add(stringMessageConverter);
Edit: How about using content negotiation manager to map text to text/plain? From javadocs
For the path extension and parameter strategies you may explicitly add MediaType mappings. This will be used to resolve path extensions or a parameter value such as "json" to a media type such as "application/json".
The docs explicitly mention extensions and parameters and I'm not completely sure but it may also work on accept headers (I would need to dig into the code more). If it doesn't you may need to look at a custom ContentNegotiationStrategy
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes" >
<value>
text=plain/text
</value>
</property>
</bean>
–
–
–
–
–
It's possible to register a ClientHttpRequestInterceptor
with a RestTemplate
which allows for editing of client responses before Spring does anything to them. To solve my problem, I created a class that implements ClientHttpRequestInterceptor
and overrode the intercept
method to replace all instances of text;
with application/json;
in the Content-Type
header. I then registered my ClientHttpRequestInterceptor
with my RestTemplate
bean definition, so that any time I autowire a RestTemplate
, it has this interceptor on it.
Code for my ClientHttpRequestInterceptor
:
public class ContentTypeTextToTextJson implements ClientHttpRequestInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(ContentTypeTextToTextJson.class);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
LOG.debug("intercepting execution");
// Get the response as normal.
ClientHttpResponse response = execution.execute(request, body);
LOG.debug("intercepted response: " + response);
// Get the headers.
HttpHeaders headers = response.getHeaders();
LOG.debug("response had headers: " + headers);
// Grab all the content types.
List<String> contentTypes = headers.get("Content-Type");
LOG.debug("response had content-types: " + contentTypes);
// Loop over the content-types.
for(int i = 0; i < contentTypes.size(); i++) {
String contentType = contentTypes.get(i);
LOG.debug("processing content type: " + contentType);
// I'm not sure if it's possible for a content-type to be null, but I guess it's
// better safe then sorry?
if(null == contentType) {
continue;
// If it starts with "text;", replace "text" with "text/json" and replace the old content type.
if(contentType.startsWith("text;")) {
contentType = contentType.replaceFirst("text", "application/json");
LOG.debug("replacing content type " + contentTypes.get(i) + " with content type " + contentType);
contentTypes.set(i, contentType);
// Return the response.
return response;
RestTemplate bean definition:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="interceptors">
<bean class="restinterceptors.ContentTypeTextToTextJson" />
</list>
</property>
</bean>
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.