No converter found capable of converting from type [com.google.protobuf.ByteString$LiteralByteString] to type [java.lang.String]

Viewed 862

Hi I´m getting a covnverter not found error when I'm trying openfeign with a custom interceptor. In the following sections I will detail what I have and what I did.

Environment: spring-boot 2.2.5.RELEASE and 2.2.6.RELEASE

Dependencies for google secret manager and openfeign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
    <version>1.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>

The Feign client Interface

I followed the documentation from spring cloud openfeign

@FeignClient(value = "myClient", url = "${WEBSERVICE_PATH}", configuration = BasicAuthFeignConfiguration.class)
public interface MyClient {

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<String> validate(@RequestHeader("Page-Description") String jwt, String request);
}

My Configuration Class

Here the AUTH_CREDENTIALS in the properties file is equal to ${sm://secret}

public class BasicAuthFeignConfiguration {

    @Value("${AUTH_CREDENTIALS}")
    private String authCredentials;

    @Bean
    public RequestInterceptor basicAuthRequestInterceptor(){
        return new CustomBasicAuthRequestInterceptor(authCredentials);
    } }

The custom interceptor

I've created a custom interceptor because the credentials returned by the secret manager is in the user:pass format, so if I use the default BasicAuthRequestInterceptor I would add a logic to split the user and password to pass to the constructor and in the inside it will build in the user:pass format again

public class CustomBasicAuthRequestInterceptor implements RequestInterceptor {

    private String headerValue;

    public CustomBasicAuthRequestInterceptor(String authCredentials, Charset charset){
        this.headerValue = "Basic " + Base64.getEncoder().encode(authCredentials.getBytes(Charset.forName("UTF-8")));

    }
    public CustomBasicAuthRequestInterceptor(String authCredentials){
        this(authCredentials, Charset.forName("UTF-8"));
    }

    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorization", headerValue);
    }
}

All of this I wrote using the documentation of the spring cloud open feign

The trouble I having is the following, when I'm running the app I'm getting this error

{"message":"Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'basicAuthFeignConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.google.protobuf.ByteString$LiteralByteString] to type [java.lang.String]"}

I was debugging and notice the converters from the secret manager are loaded but when the feign client loads the configuration there no converters so it loads the defaults and that converter is not in the defaults.

If I annotate the class with de @Configuration it parses the ByteString to String but it applies to all the feign clients and that behavior I don't want.

So finally, is there something I missing? Something I'm doing wrong?

1 Answers

I fell into a similar situation recently and after pulling my hair for a long time and debugging, this is how I could make it work. First of all, create a generic converter. This code is in Kotlin:

class GcpStringGenericConverter : GenericConverter {

    override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair> {
        return mutableSetOf(GenericConverter.ConvertiblePair(ByteString::class.java, String::class.java))
    }

    override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? {
        return (source as? ByteString)?.toString(UTF_8)
    }

}

After that, register this converter with DefaultConversionService and the only way possible for me was to register it even before starting the Spring application.

fun main(args: Array<String>) {
    (DefaultConversionService.getSharedInstance() as? DefaultConversionService)?.addConverter(GcpStringGenericConverter())
    runApplication<MyApplication>(*args)
}
Related