Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Handling Custom Decoders for Void Return Types in Spring Cloud OpenFeign

Tech 1

When implementing remote service integrations with Spring Cloud OpenFeign, a common pattern involves wrapping outgoing requetss and incoming responses in a unified protocol structure. Developers typically register a custom Decoder to extract payload data, validate HTTP status codes, and rethrow remote business exceptions. A specific edge case arises when the target interface declares a void return type. In these scenarios, the framework's internal request pipeline completely skips the custom decoding stage, leaving exception handling untriggered and returning a successful completion state regardless of the actual response body.

Tracing the execution stack reveals that this short-circuit is intentional within OpenFeign's architecture. The AsyncResponseHandler component evaluates the declared method signature early in the response cycle. If the signature indicates a void return, the handler fulfills the underlying CompletableFuture with null without invoking the registered Decoder. This bypass mechanism was historically gated behind an internal boolean property named forceDecoding inside the synchronous method handler factory. Disabling this optimization ensures the full serialization/deserialization chain executes.

public class UnifiedApiDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type targetType) throws IOException {
        String responseBody = Util.toString(response.body().asReader(Util.UTF_8));
        ApiResponseWrapper wrapper = JsonParser.readValue(responseBody, ApiResponseWrapper.class);

        if (!wrapper.isSuccess()) {
            throw new RemoteServiceFailureException(wrapper.getErrorDetails());
        }

        return JsonParser.convertValue(wrapper.getPayload(), targetType);
    }
}

For legacy OpenFeign versions (typically aligned with Spring Boot 2.x), enabling full decoding requires injecting the decoder into a configuration bean and forcing the internal flag via reflection:

@Configuration
public class FeignClientConfiguration {

    @Bean
    public UnifiedApiDecoder apiDecoder() {
        return new UnifiedApiDecoder();
    }

    @Bean
    public Feign.Builder feignBuilder(UnifiedApiDecoder decoder) throws Exception {
        Feign.Builder clientBuilder = Feign.builder().decoder(apiDecoder());
        
        java.lang.reflect.Field forceDecodeFlag = clientBuilder.getClass()
                .getDeclaredField("forceDecoding");
        forceDecodeFlag.setAccessible(true);
        forceDecodeFlag.set(clientBuilder, true);
        
        return clientBuilder;
    }
}

Once the decoding pipeline is forced, the custom decoder must explicit account for void return types to prevent unnecessary payload trensformation or casting exceptions. The validation logic should execute first, followed by a type check that returns null for void signatures:

public class UnifiedApiDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type targetType) throws IOException {
        String rawPayload = Util.toString(response.body().asReader(Util.UTF_8));
        ApiResponseWrapper responseStruct = JsonParser.readValue(rawPayload, ApiResponseWrapper.class);

        if (!responseStruct.isSuccess()) {
            throw new RemoteServiceFailureException(responseStruct.getErrorDetails());
        }

        boolean isVoidReturn = targetType == Void.class || targetType == Void.TYPE;
        if (isVoidReturn) {
            return null;
        }

        return JsonParser.convertValue(responseStruct.getData(), targetType);
    }
}

Recent iterations of OpenFeign (standardized in Spring Boot 3.x+) refactored this internal state management. The opaque forceDecoding field was replaced with a public builder fluent API designed specifically for this scenario. Developers no longer need reflection to override the default behavior. Instead, the builder exposes a direct method that guarantees the decoder processes all response streams, including those intended for void methods.

@Configuration
public class ModernFeignConfiguration {

    @Bean
    public UnifiedApiDecoder apiDecoder() {
        return new UnifiedApiDecoder();
    }

    @Bean
    public Feign.Builder feignBuilder(UnifiedApiDecoder decoder) {
        return Feign.builder()
                .decoder(apiDecoder())
                .decodeVoid();
    }
}

By routing requests through the complete decoding lifecycle, exception interception becomes consistent across all endpoint signatures. This approach maintains strict control over network traffic and ensures that centralized error handling remains active regardless of whether a microservice method produces a return value or operates purely as a side-effect trigger.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.