Why is Spring de-coding + (the plus character) on application/json get requests? and what should I do about it?
Original Answer
You are mixing 2 things, a +
in the body of the request would mean a space when header has application/x-www-form-urlencoded
. The body or content of the request would be dependent on the headers but a request can just have a url
and no headers
and no body
.
So the encoding of a URI cannot be controlled by any headers as such
See the URL Encoding
section in https://en.wikipedia.org/wiki/Query_string
Some characters cannot be part of a URL (for example, the space) and some other characters have a special meaning in a URL: for example, the character # can be used to further specify a subsection (or fragment) of a document. In HTML forms, the character = is used to separate a name from a value. The URI generic syntax uses URL encoding to deal with this problem, while HTML forms make some additional substitutions rather than applying percent encoding for all such characters. SPACE is encoded as '+' or "%20".[10]
HTML 5 specifies the following transformation for submitting HTML forms with the "get" method to a web server.1 The following is a brief summary of the algorithm:
Characters that cannot be converted to the correct charset are replaced with HTML numeric character references[11] SPACE is encoded as '+' or '%20' Letters (A–Z and a–z), numbers (0–9) and the characters '*','-','.' and '_' are left as-is All other characters are encoded as %HH hex representation with any non-ASCII characters first encoded as UTF-8 (or other specified encoding) The octet corresponding to the tilde ("~") is permitted in query strings by RFC3986 but required to be percent-encoded in HTML forms to "%7E".
The encoding of SPACE as '+' and the selection of "as-is" characters distinguishes this encoding from RFC 3986.
And you can see the same behaviour on google.com
as well from below screenshots
Also you can see the same behaviour in other frameworks as well. Below is an example of Python Flask
So what you are seeing is correct, you are just comparing it with a document which refers to the body content of a request and not the URL
Edit-1: 22nd May
After debugging it seems the decoding doesn't even happen in Spring. I happens in package org.apache.tomcat.util.buf;
and the UDecoder
class
/**
* URLDecode, will modify the source.
* @param mb The URL encoded bytes
* @param query <code>true</code> if this is a query string
* @throws IOException Invalid %xx URL encoding
*/
public void convert( ByteChunk mb, boolean query )
throws IOException
{
int start=mb.getOffset();
And below is where the conversion stuff actually happens
if( buff[ j ] == '+' && query) {
buff[idx]= (byte)' ' ;
} else if( buff[ j ] != '%' ) {
This means that it is an embedded tomcat server which does this translation and spring doesn't even participate in this. There is no config to change this behaviour as seen in the class code. So you have to live with it
SPR-6291 fixed this problem in v3.0.5
but this remains unresolved in some other cases like SPR-11047 is still unresolved. While SPR-6291's priority was Major, SPR-11047's priority is Minor.
I faced this problem when I was working on REST API in old Spring last year. There are multiple ways we can get data in Spring controller
. So two of them are via @RequestParam
or @PathVariable
annotation
As others mentioned I think its spring's internal issue and does not specifically belong to URL
encoding because I was sending data over POST
request but it is somewhat encoding problem. But I also agree with others as now it remains problematic only in URL
.
So there are two solutions I know:
You can use
@PathVariable
instead of@RequestParam
because as ofSPR-6291
this plus sign issue is fixed in@PathVariable
and still remains open for@RequestParam
asSPR-11047
My version of spring was not even accepting plus sign via
@PathVariable
annotation, so this is how I overcome the problem (I don't remember it step by step but it will give you hint).
In your case you can get the fields via JS
and escape
the plus sign before sending a request. Something like this:
var email = document.getElementById("emailField").value;
email = email.replace('+', '%2B');
If you have this request:
http://localhost/[email protected]
then the original is foo [email protected]
. If you say the original should be [email protected]
then the request should be:
http://localhost/foo?email=foo%[email protected]
So Spring is working as supposed to. Maybe on client you should check if the URI is properly encoded. The client-side URL encoding is responsible for building a correct HTTP request.
See encodeURI() if you generate the request in JavaScript or uriToString() if you generate the request in Spring.
Build your request string (the part after ?
), without any encoding, with unencoded values like [email protected]
, and only in the end, before actually using it in GET
, encode all of it with whatever is available on the client platform. If you want to use POST
then you should encode it according to the MIME type of your choice.