Retrofit: Using @QueryMap(encoded = true) will still encode values

Created on 14 Oct 2015  路  18Comments  路  Source: square/retrofit

I'm using QueryMap like this:

interface ProjectApi {
    @GET("projects/")
    Observable<Project> getProjects(@QueryMap(encoded = true) Map<String, String> params);
}
final ProjectApi api = ...;
final Map<String, String> params = new HashMap<>();

params.put("owner", 1234);
params.put("statuses[]", "ACCEPTED&statuses[]=IN_PROGRESS");

api.getProjects(params)

I assume that when setting encoded to true, values will not be encoded anymore. However this is not the case, ACCEPTED&statuses[]=IN_PROGRESS gets encoded to ACCEPTED%26statuses%5B%5D%3DIN_PROGRESS

I'm using 2.0.0-beta2.

Most helpful comment

I think writing custom interceptor is one way to solve this issue, I think it is not pefect but it works for now. Add custom interceptor to OkHttp with intercept method like below:

@Override
Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request()
    def string = request.url().toString()
    string = string.replace("%26", "&")
    string = string.replace("%3D", "=")

    Request newRequest = new Request.Builder()
        .url(string)
        .build()

    return chain.proceed(newRequest)
}

Above code is just simple example, it is lacking headers, etc.

All 18 comments

For what it's worth, we're just calling OkHttp's addEncodedQueryParam method and specifying true as the last argument. So the behavior you are seeing is from OkHttp.

What you're really looking for is iterable values for these annotations so that you can just pass a list of values with a single name declaration instead of the map hack...

Iterable map-based params is covered under #685.

I'm experiencing the same incorrect behaviour with @Query annotated parameters

Interface declaration
@Query(value="start_date", encoded=true) String start,
@Query(value="end_date", encoded=true) String end,

In Java, already encoded
String start_date = "2015-11-19+11%3A32%3A22.824-0300"
String end_date = "2015-11-23+11%3A32%3A22.824-0300"

In the wire, encoded twice
start_date=2015-11-19%2B11%253A32%253A22.824-0300
end_date=2015-11-23%2B11%253A32%253A22.824-0300

Retrofit 2.0.0-beta2
OkHttp 2.6.0

Can the root cause be the same?

me too

@GET(Constants.LIVE_CHAT_ROOM_INFO)
    Observable<RoomInfo> requestChatRoomInfo(@QueryMap(encoded = true) Map data);

but result is:

http://data.chat.126.net/route_room?avatar=http://imgm.ph.126.net/ObtaShfyoAJr160qkBnlFw%3D%3D/6631275573400909981.png&userid=ZhQic0pQy41ku3mzYK9Iiw%3D%3D&nickname=cyning&topicid=75053

the issule is not Ok!

Hi, I am facing issue with encoding as well:
I need to send redirect_url parameter and is it not encoded when I pass it as parameter to the interface method:

    @POST("token/?grant_type=authorization_code")
    Call<AccessToken> getAccessTokenWithCode(
            @Query("code") String code,
            @Query("redirect_uri") String redirectUri
    );

In actual request url redirect_uri value is not encoded:
https://.../token?grant_type=authorization_code&code=myCode&redirect_uri=https://my_redirect_url/token

While I am expecting it to be encoded by Retrofit/OkHttp.

I have tried setting encoded value to false (which is default value) and still it does not help:
@Query(value = "redirect_uri", encoded = false) String redirectUri

Is this resolved? I am having the same problem here.

Same problem;
Using compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1

@sergii-frost which character do you expect to be encoded?

@evgenizero, @JoaoManaroulas I suspect this is a problem with OkHttp鈥檚 HttpUrl.

Could you each please provide an executable test case like this one that demonstrates the differences between your expectations and our behavior?

  @Test public void composeQueryWithEncodedComponents() throws Exception {
    HttpUrl base = HttpUrl.parse("http://host/");
    HttpUrl url = base.newBuilder().addEncodedQueryParameter("a+=& b", "c+=& d").build();
    assertEquals("http://host/?a+%3D%26%20b=c+%3D%26%20d", url.toString());
    assertEquals("c =& d", url.queryParameter("a =& b"));
  }

@swankjesse I am expecting "://" symbols to be encoded in the url.

I was able to that working as expected when using @FormUrlEncoded and @Field instead of @Query - in this case all the fields work as expected.

hi @swankjesse I am having same problem when encoding an '=' . The field is a token that has an equal sign at the end. I am trying to send it over as
@Query(value = "authSignature",encoded = true) String authSignature

I am using retrofit 2.0.0-beta3. What I ended up needing to do is something like this in interceptor:
request.url(originalRequest.url().toString().replace("%3D","="));

Hi all! I've looked into okhttp source and found the problem.
addEncodedQueryParameter() does encode keys and values of the url, but does not re-encode "%" symbol.

So, one of the solutions is to use @Query("fieldName") List<String> values

@JakeWharton I can't find 锛歄kHttp's addEncodedQueryParam

HI all. Had the same problem and I've been searching for a fast solution. For me it was to use retrofit dynamic urls. I have manually converted values in a Map to get params, checked that they are not encoded and that's it.

I think writing custom interceptor is one way to solve this issue, I think it is not pefect but it works for now. Add custom interceptor to OkHttp with intercept method like below:

@Override
Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request()
    def string = request.url().toString()
    string = string.replace("%26", "&")
    string = string.replace("%3D", "=")

    Request newRequest = new Request.Builder()
        .url(string)
        .build()

    return chain.proceed(newRequest)
}

Above code is just simple example, it is lacking headers, etc.

really works) thx)

fun getClient(codeForActionOrComedy:Int): MovieDbInterfaceToGetMovies {

    val requestToApiInterceptor=Interceptor { chain ->

        val url =chain.request()
            .url()
            .newBuilder()
            .addQueryParameter("api_key",ALONE_API)
                .addQueryParameter("with_genres","$codeForActionOrComedy")
            .build()

        val request =chain.request()
            .newBuilder()
            .url(url.toString().replace("%3D","="))
            .build()
        return@Interceptor chain.proceed(request)
    }
    val okHttpClient= OkHttpClient.Builder()
        .addInterceptor(requestToApiInterceptor)
        .connectTimeout(60, TimeUnit.SECONDS)
        .build()

    val httpUrl = Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(BASE_URL)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(MovieDbInterfaceToGetMovies::class.java)

    return httpUrl

Hi the url is :https://api.themoviedb.org/3/discover/movie?api_key={"PUTAPIKEYHERE!!!}&with_genres=28
i was getting :https://api.themoviedb.org/3/discover/movie?api_key={"PUTAPIKEYHERE!!!}&with_genres%3D=28

fun getClient(codeForActionOrComedy:Int): MovieDbInterfaceToGetMovies {

        val requestToApiInterceptor=Interceptor { chain ->

            val url =chain.request()
                .url()
                .newBuilder()
                .addQueryParameter("api_key",ALONE_API)
                    .addQueryParameter("with_genres","$codeForActionOrComedy")
                .build()

            val request =chain.request()
                .newBuilder()
                .url(url.toString().replace("%3D","="))
                .build()
            return@Interceptor chain.proceed(request)
        }
        val okHttpClient= OkHttpClient.Builder()
            .addInterceptor(requestToApiInterceptor)
            .connectTimeout(60, TimeUnit.SECONDS)
            .build()

        val httpUrl = Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(MovieDbInterfaceToGetMovies::class.java)

        return httpUrl
Was this page helpful?
0 / 5 - 0 ratings

Related issues

chriskessel picture chriskessel  路  3Comments

kkunsue picture kkunsue  路  3Comments

attaullahpro picture attaullahpro  路  3Comments

Ne1c picture Ne1c  路  3Comments

colintheshots picture colintheshots  路  3Comments