{"action":"created","issue":{"url":"https://api.github.com/repos/Netflix/feign/issues/262","labels_url":"https://api.github.com/repos/Netflix/feign/issues/262/labels{/name}","comments_url":"https://api.github.com/repos/Netflix/feign/issues/262/comments","events_url":"https://api.github.com/repos/Netflix/feign/issues/262/events","html_url":"https://github.com/Netflix/feign/issues/262","id":104438097,"number":262,"title":"@Param fails to pick up expansion of @Headers on interface","user":{"login":"stromnet","id":668449,"avatar_url":"https://avatars.githubusercontent.com/u/668449?v=3","gravatar_id":"","url":"https://api.github.com/users/stromnet","html_url":"https://github.com/stromnet","followers_url":"https://api.github.com/users/stromnet/followers","following_url":"https://api.github.com/users/stromnet/following{/other_user}","gists_url":"https://api.github.com/users/stromnet/gists{/gist_id}","starred_url":"https://api.github.com/users/stromnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stromnet/subscriptions","organizations_url":"https://api.github.com/users/stromnet/orgs","repos_url":"https://api.github.com/users/stromnet/repos","events_url":"https://api.github.com/users/stromnet/events{/privacy}","received_events_url":"https://api.github.com/users/stromnet/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-09-02T09:28:24Z","updated_at":"2015-09-03T14:24:00Z","closed_at":null,"body":"Hi,\r\n\r\nI'm experimenting with @Headers on the interface class, rather than each individual method. In my case I want an \"Authorization: xxx\" header which should be populated at each call:\r\n```\r\n@Headers(\"Authorization: {authHeader}\")\r\npublic interface SomeApi {\r\n        @RequestLine(\"GET /someobj?match={match}\")\r\n        List<SomeObj> getSomeObj(@Param(\"match\") String match, @Param(\"authHeader\") String authHeader);\r\n}\r\n```\r\n\r\nIt seems the Default Contract implementation will first process annotations on all methods, including @Param annotations, and then it will process annotations on the interface. During the analyze of @Param annotations it does not find the {authHeader} in the headers() (since it has not yet been processed), and thus adds it as a formParam.\r\n\r\nDuring execution it will fail, when it uses BuildFormEncodedTemplateFromArgs, and no body is specified (same as #259).\r\n\r\nExpected: that it would map the @Param to the @Headers on the interface.\r\nWorkaround: Move the @Headers annotation to each method."},"comment":{"url":"https://api.github.com/repos/Netflix/feign/issues/comments/137464995","html_url":"https://github.com/Netflix/feign/issues/262#issuecomment-137464995","issue_url":"https://api.github.com/repos/Netflix/feign/issues/262","id":137464995,"user":{"login":"stromnet","id":668449,"avatar_url":"https://avatars.githubusercontent.com/u/668449?v=3","gravatar_id":"","url":"https://api.github.com/users/stromnet","html_url":"https://github.com/stromnet","followers_url":"https://api.github.com/users/stromnet/followers","following_url":"https://api.github.com/users/stromnet/following{/other_user}","gists_url":"https://api.github.com/users/stromnet/gists{/gist_id}","starred_url":"https://api.github.com/users/stromnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stromnet/subscriptions","organizations_url":"https://api.github.com/users/stromnet/orgs","repos_url":"https://api.github.com/users/stromnet/repos","events_url":"https://api.github.com/users/stromnet/events{/privacy}","received_events_url":"https://api.github.com/users/stromnet/received_events","type":"User","site_admin":false},"created_at":"2015-09-03T14:24:00Z","updated_at":"2015-09-03T14:24:00Z","body":"Could something like the following cut it, without breaking breaking compatibility?\r\n\r\nPseudo-patch:\r\n```    \r\nclass BaseContract:\r\n\r\n+protected void headersFromAnnotation(Class<?> targetType, MethodMetadata data) {\r\n+}\r\n\r\nprotected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {\r\n      MethodMetadata data = new MethodMetadata();\r\n      data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));\r\n      data.configKey(Feign.configKey(targetType, method));\r\n+     headersFromAnnotation(method.getDeclaringClass(), data);\r\n+     if (method.getDeclaringClass() != targetType) {\r\n+        headersFromAnnotation(targetType, data);\r\n+     }\r\n      for (Annotation methodAnnotation : method.getAnnotations()) {\r\n        processAnnotationOnMethod(data, methodAnnotation, method);\r\n      }\r\n      checkState(data.template().method() != null,\r\n                 \"Method %s not annotated with HTTP method type (ex. GET, POST)\",\r\n                 method.getName());\r\n      Class<?>[] parameterTypes = method.getParameterTypes();\r\n\r\n```\r\n```\r\nclass Default extends BaseContract {\r\n\r\n-  @Override\r\n-    protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {\r\n-      MethodMetadata data = super.parseAndValidateMetadata(targetType, method);\r\n-      headersFromAnnotation(method.getDeclaringClass(), data);\r\n-      if (method.getDeclaringClass() != targetType) {\r\n-        headersFromAnnotation(targetType, data);\r\n-      }\r\n-      return data;\r\n-    }\r\n\r\n-    private void headersFromAnnotation(Class<?> targetType, MethodMetadata data) {\r\n+    @Override\r\n+    protected void headersFromAnnotation(Class<?> targetType, MethodMetadata data) {\r\n\r\n```\r\n\r\nThus, for Default, headersFromAnnotation will be called a bit earlier. For any others, a no-op function will be called unless overridden."}}