title: QueryDsl web query on the key of a Map field
body: <h1>Overview</h1>
  
  <h2>Given</h2>
  
  <ul>
  <li>Spring Data JPA, Spring Data Rest, QueryDsl</li>
  <li>a <code>Meetup</code> entity
  
  <ul>
  <li>with a <code>Map&lt;String,String&gt; properties</code> field
  
  <ul>
  <li>persisted in a <code>MEETUP_PROPERTY</code> table as an <code>@ElementCollection</code></li>
  </ul></li>
  </ul></li>
  <li>a <code>MeetupRepository</code>
  
  <ul>
  <li>that extends <code>QueryDslPredicateExecutor&lt;Meetup&gt;</code></li>
  </ul></li>
  </ul>
  
  <h2>I'd expect</h2>
  
  <p>A web query of</p>
  
  <pre><code>GET /api/meetup?properties[aKey]=aValue
  </code></pre>
  
  <p>to return only Meetups with a property entry that has the specified key and value: aKey=aValue.</p>
  
  <p>However, that's not working for me.
  What am I missing?</p>
  
  <h1>Tried</h1>
  
  <h3>Simple Fields</h3>
  
  <p>Simple fields work, like name and description:</p>
  
  <pre><code>GET /api/meetup?name=whatever
  </code></pre>
  
  <p>Collection fields work, like participants:</p>
  
  <pre><code>GET /api/meetup?participants.name=whatever
  </code></pre>
  
  <p>But not this Map field.</p>
  
  <h3>Customize QueryDsl bindings</h3>
  
  <p>I've tried customizing the binding by having the repository </p>
  
  <pre><code>extend QuerydslBinderCustomizer&lt;QMeetup&gt;
  </code></pre>
  
  <p>and overriding the </p>
  
  <pre><code>customize(QuerydslBindings bindings, QMeetup meetup)
  </code></pre>
  
  <p>method, but while the <code>customize()</code> method is being hit, the binding code inside the lambda is not.</p>
  
  <p>EDIT: Learned that's because <code>QuerydslBindings</code> means of evaluating the query parameter do not let it match up against the <code>pathSpecs</code> map it's internally holding - which has your custom bindings in it.</p>
  
  <h1>Some Specifics</h1>
  
  <h2>Meetup.properties field</h2>
  
  <pre><code>@ElementCollection(fetch = FetchType.EAGER)
  @CollectionTable(name = "MEETUP_PROPERTY", joinColumns = @JoinColumn(name = "MEETUP_ID"))
  @MapKeyColumn(name = "KEY")
  @Column(name = "VALUE", length = 2048)
  private Map&lt;String, String&gt; properties = new HashMap&lt;&gt;();
  </code></pre>
  
  <h2>customized querydsl binding</h2>
  
  <p><strong>EDIT:</strong> See above; turns out, this was doing nothing for my code.</p>
  
  <pre><code>public interface MeetupRepository extends PagingAndSortingRepository&lt;Meetup, Long&gt;,
                                            QueryDslPredicateExecutor&lt;Meetup&gt;,
                                            QuerydslBinderCustomizer&lt;QMeetup&gt; {
  
      @Override
      default void customize(QuerydslBindings bindings, QMeetup meetup) {
          bindings.bind(meetup.properties).first((path, value) -&gt; {
              BooleanBuilder builder = new BooleanBuilder();
              for (String key : value.keySet()) {
                  builder.and(path.containsKey(key).and(path.get(key).eq(value.get(key))));
              }
              return builder;
          });
  }
  </code></pre>
  
  <h1>Additional Findings</h1>
  
  <ol>
  <li><code>QuerydslPredicateBuilder.getPredicate()</code> asks <code>QuerydslBindings.getPropertyPath()</code> to try 2 ways to return a path from so it can make a predicate that <code>QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()</code> can use.
  
  <ul>
  <li>1 is to look in the customized bindings. I don't see any way to express a map query there</li>
  <li>2 is to default to Spring's bean paths. Same expression problem there. How do you express a map?
  So it looks impossible to get <code>QuerydslPredicateBuilder.getPredicate()</code> to automatically create a predicate.
  Fine - I can do it manually, if I can hook into <code>QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver.postProcess()</code></li>
  </ul></li>
  </ol>
  
  <p>HOW can I override that class, or replace the bean? It's instantiated and returned as a bean in the <code>RepositoryRestMvcConfiguration.repoRequestArgumentResolver()</code> bean declaration.</p>
  
  <ol start="2">
  <li>I <strong>can</strong> override that bean by declaring my own <code>repoRequestArgumentResolver</code> bean, but it doesn't get used. 
  
  <ul>
  <li>It gets overridden by <code>RepositoryRestMvcConfiguration</code>s. I can't <strong>force</strong> it by setting it <code>@Primary</code> or <code>@Ordered(HIGHEST_PRECEDENCE)</code>. </li>
  <li>I <strong>can</strong> force it by explicitly component-scanning <code>RepositoryRestMvcConfiguration.class</code>, but that also messes up Spring Boot's autoconfiguration because it causes
  <code>RepositoryRestMvcConfiguration's</code> bean declarations to be processed
  before any auto-configuration runs. Among other things, that results in responses that are serialized by Jackson in unwanted ways.</li>
  </ul></li>
  </ol>
  
  <h1>The Question</h1>
  
  <p>Well - looks like the support I expected just isn't there.</p>
  
  <p>So the question becomes:
  <strong>HOW</strong> do I correctly override the <code>repoRequestArgumentResolver</code> bean?</p>
  
  <p>BTW - <code>QuerydslAwareRootResourceInformationHandlerMethodArgumentResolver</code> is awkwardly non-public. :/</p>