Where the Problem Started

FeignClient is convenient because it is declarative, but the actual request still depends on the underlying HTTP client implementation. That means a method can fail even when the annotation looks correct. This post follows a PATCH failure and records the points worth checking.

FeignClient is Spring Cloud’s HTTP client. When configuring a multi-module environment such as microservices, it makes it easy to exchange HTTP request and response data.

There are alternatives such as RestTemplate and WebClient, but FeignClient has the advantage that the code is much easier to read and more concise.

Implementation Path

implementation("org.springframework.cloud:spring-cloud-starter-feign:1.4.7.RELEASE")

Add the dependency with the version that matches the project.

@FeignClient(name = "feignclient")
sealed interface FeignController {
    @PatchMapping("/test")
    fun test(
        @RequestBody
        body: String,
    ): String
}

One module declares the Feign client:

@RestController
class Controller {
    @PatchMapping("/test")
    fun test(
        @RequestBody
        body: String,
    ): String {
        return "test"
    }
}

Another module exposes the matching controller. As long as the URI, request arguments, and response type line up, the two modules can communicate. The strange part appeared only when the method was PATCH, so I started from the logs.

java.net.ProtocolException: Invalid HTTP method: PATCH
	at java.base/java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:489) ~[na:na]
	at java.base/sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:598) ~[na:na]
	at feign.Client$Default.convertAndSend(Client.java:171) ~[feign-core-13.1.jar:na]
	at feign.Client$Default.execute(Client.java:106) ~[feign-core-13.1.jar:na]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:100) ~[feign-core-13.1.jar:na]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:70) ~[feign-core-13.1.jar:na]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:99) ~[feign-core-13.1.jar:na]
	at jdk.proxy3/jdk.proxy3.$Proxy73.test(Unknown Source) ~[na:na]

The message says PATCH, a normal HTTP method, is invalid. The second line points to the real cause. Without additional configuration, Feign uses Java’s HttpURLConnection as the underlying HTTP client. Opening that library code in IntelliJ makes the limitation visible.

    /* valid HTTP methods */
    private static final String[] methods = {
        "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
    };

The supported method list is hard-coded like this. Historically, HTTP/1.1 RFC 2068 was published in January 1997: https://www.rfc-editor.org/info/rfc2068. HttpURLConnection was included with Java 1.1, around that era. PATCH arrived much later in RFC 5789, published in March 2010. Because PATCH is meant for partial modification rather than full replacement, its semantics are different from PUT, and that late standardization shows up as a limitation in older client implementations.

The fix is to change the HTTP client implementation used by Feign. In this case, I used OkHttp.

implementation("io.github.openfeign","feign-okhttp")

Add the dependency, then register an OkHttp-backed Feign client. Create a client bean and reference that configuration from the @FeignClient annotation.

@Configuration
class FeignOkHttpConfiguration {
    @Bean
    fun client(): OkHttpClient {
        return OkHttpClient()
    }
}
@FeignClient(name = "feign", configuration = [FeignOkHttpConfiguration::class])

Debugging Takeaway

HTTP client issues are hard to see from the controller or API spec alone. Feign configuration, the underlying client, dependency versions, and method support all need to be checked together. Bugs like this are useful reminders to inspect where an abstraction ends and the concrete implementation begins.