or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

codecs.mdhttp-clients.mdhystrix.mdindex.mdjaxrs.mdjson-processing.mdxml-processing.md
tile.json

codecs.mddocs/

Encoders and Decoders

Feign's codec system provides pluggable request encoding and response decoding through the Encoder and Decoder interfaces, enabling support for various data formats and custom serialization logic.

Capabilities

Encoder Interface

Request body encoding interface for converting objects to request bodies.

/**
 * Encodes objects into HTTP request bodies
 */
public interface Encoder {
  /** 
   * Encode object to request body
   * @param object Object to encode
   * @param bodyType Type of the object
   * @param template Request template to modify
   * @throws EncodeException if encoding fails
   */
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
  
  /**
   * Default encoder that handles String and byte[] objects
   */
  public static class Default implements Encoder {
    /** Encode String and byte[] objects, throw exception for others */
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
  }
}

/**
 * Exception thrown when encoding fails
 */
public class EncodeException extends FeignException {
  public EncodeException(String message);
  public EncodeException(String message, Throwable cause);
}

Decoder Interface

Response body decoding interface for converting HTTP responses to objects.

/**
 * Decodes HTTP response bodies into objects
 */
public interface Decoder {
  /**
   * Decode response body to object
   * @param response HTTP response
   * @param type Target type to decode to
   * @return Decoded object
   * @throws IOException if I/O error occurs
   * @throws DecodeException if decoding fails
   * @throws FeignException for HTTP-level errors
   */
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
  
  /**
   * Default decoder that handles String, byte[], and provides 404 handling
   */
  public static class Default implements Decoder {
    /** Decode to String, byte[], or handle 404 responses */
    public Object decode(Response response, Type type) throws IOException;
  }
}

/**
 * Exception thrown when decoding fails
 */
public class DecodeException extends FeignException {
  public DecodeException(String message);
  public DecodeException(String message, Throwable cause);
}

Error Decoder Interface

Specialized decoder for handling HTTP error responses.

/**
 * Decodes HTTP error responses into exceptions
 */
public interface ErrorDecoder {
  /**
   * Convert error response to exception
   * @param methodKey Configuration key for the method
   * @param response HTTP error response
   * @return Exception to throw, or null to use default behavior
   */
  Exception decode(String methodKey, Response response);
  
  /**
   * Default error decoder that creates FeignException for non-2xx responses
   */
  public static class Default implements ErrorDecoder {
    /** Create FeignException based on response status */
    public Exception decode(String methodKey, Response response);
  }
}

String Decoder

Simple decoder for converting responses to strings.

/**
 * Decoder that converts response bodies to strings
 */
public class StringDecoder implements Decoder {
  /** Decode response body to string using response charset or UTF-8 */
  public Object decode(Response response, Type type) throws IOException;
}

Usage Examples

Custom Encoder Implementation

import feign.Encoder;
import feign.RequestTemplate;
import java.lang.reflect.Type;

// Custom encoder for form data
public class FormEncoder implements Encoder {
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (object instanceof Map) {
            Map<String, Object> formData = (Map<String, Object>) object;
            StringBuilder body = new StringBuilder();
            
            for (Map.Entry<String, Object> entry : formData.entrySet()) {
                if (body.length() > 0) {
                    body.append("&");
                }
                body.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                    .append("=")
                    .append(URLEncoder.encode(String.valueOf(entry.getValue()), "UTF-8"));
            }
            
            template.header("Content-Type", "application/x-www-form-urlencoded");
            template.body(body.toString().getBytes("UTF-8"), Charset.forName("UTF-8"));
        } else {
            throw new EncodeException("FormEncoder only supports Map objects");
        }
    }
}

// Use custom encoder
API api = Feign.builder()
    .encoder(new FormEncoder())
    .target(API.class, "https://api.example.com");

Custom Decoder Implementation

import feign.Decoder;
import feign.Response;
import java.lang.reflect.Type;

// Custom decoder for CSV data
public class CSVDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type type) throws IOException {
        if (type == List.class) {
            List<Map<String, String>> records = new ArrayList<>();
            
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().asInputStream()))) {
                String headerLine = reader.readLine();
                String[] headers = headerLine.split(",");
                
                String line;
                while ((line = reader.readLine()) != null) {
                    String[] values = line.split(",");
                    Map<String, String> record = new HashMap<>();
                    
                    for (int i = 0; i < headers.length && i < values.length; i++) {
                        record.put(headers[i].trim(), values[i].trim());
                    }
                    records.add(record);
                }
            }
            return records;
        }
        throw new DecodeException("CSVDecoder only supports List<Map<String, String>>");
    }
}

// Use custom decoder
API api = Feign.builder()
    .decoder(new CSVDecoder())
    .target(API.class, "https://api.example.com");

Custom Error Decoder

import feign.ErrorDecoder;
import feign.Response;
import feign.gson.GsonDecoder;

// Custom error decoder that parses JSON error responses
public class CustomErrorDecoder implements ErrorDecoder {
    private final Decoder decoder = new GsonDecoder();
    
    @Override
    public Exception decode(String methodKey, Response response) {
        try {
            if (response.status() >= 400) {
                ErrorResponse errorResponse = (ErrorResponse) decoder.decode(response, ErrorResponse.class);
                
                switch (response.status()) {
                    case 400:
                        return new BadRequestException(errorResponse.getMessage());
                    case 401:
                        return new UnauthorizedException(errorResponse.getMessage());
                    case 404:
                        return new NotFoundException(errorResponse.getMessage());
                    case 500:
                        return new ServerErrorException(errorResponse.getMessage());
                    default:
                        return new CustomAPIException(errorResponse.getMessage(), response.status());
                }
            }
        } catch (Exception e) {
            // Fall back to default behavior if parsing fails
        }
        
        // Return null to use default error handling
        return null;
    }
}

// Use custom error decoder
API api = Feign.builder()
    .errorDecoder(new CustomErrorDecoder())
    .target(API.class, "https://api.example.com");

Conditional Encoding/Decoding

// Encoder that handles multiple formats
public class MultiFormatEncoder implements Encoder {
    private final Encoder jsonEncoder = new GsonEncoder();
    private final Encoder xmlEncoder = new JAXBEncoder();
    
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        Collection<String> contentType = template.headers().get("Content-Type");
        
        if (contentType != null && contentType.contains("application/xml")) {
            xmlEncoder.encode(object, bodyType, template);
        } else {
            // Default to JSON
            template.header("Content-Type", "application/json");
            jsonEncoder.encode(object, bodyType, template);
        }
    }
}

// Decoder that handles multiple formats
public class MultiFormatDecoder implements Decoder {
    private final Decoder jsonDecoder = new GsonDecoder();
    private final Decoder xmlDecoder = new JAXBDecoder();
    
    @Override
    public Object decode(Response response, Type type) throws IOException {
        String contentType = response.headers().get("Content-Type").iterator().next();
        
        if (contentType.contains("application/xml")) {
            return xmlDecoder.decode(response, type);
        } else {
            return jsonDecoder.decode(response, type);
        }
    }
}

Compression Support

// Decoder with gzip decompression
public class GzipDecoder implements Decoder {
    private final Decoder delegate;
    
    public GzipDecoder(Decoder delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Object decode(Response response, Type type) throws IOException {
        String encoding = response.headers().get("Content-Encoding").iterator().next();
        
        if ("gzip".equals(encoding)) {
            // Create new response with decompressed body
            Response decompressedResponse = response.toBuilder()
                .body(new GZIPInputStream(response.body().asInputStream()), null)
                .build();
            return delegate.decode(decompressedResponse, type);
        }
        
        return delegate.decode(response, type);
    }
}

// Use with compression
API api = Feign.builder()
    .decoder(new GzipDecoder(new GsonDecoder()))
    .requestInterceptor(template -> template.header("Accept-Encoding", "gzip"))
    .target(API.class, "https://api.example.com");

Caching Decoder

// Decoder with response caching
public class CachingDecoder implements Decoder {
    private final Decoder delegate;
    private final Map<String, Object> cache = new ConcurrentHashMap<>();
    
    public CachingDecoder(Decoder delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Object decode(Response response, Type type) throws IOException {
        String cacheKey = response.request().url() + ":" + type.toString();
        
        // Check cache for GET requests with 200 status
        if ("GET".equals(response.request().method()) && response.status() == 200) {
            Object cached = cache.get(cacheKey);
            if (cached != null) {
                return cached;
            }
        }
        
        Object result = delegate.decode(response, type);
        
        // Cache successful GET responses
        if ("GET".equals(response.request().method()) && response.status() == 200) {
            cache.put(cacheKey, result);
        }
        
        return result;
    }
}

Built-in Codec Examples

Default Encoder Usage

// Default encoder handles String and byte[]
interface API {
    @RequestLine("POST /data")
    @Headers("Content-Type: text/plain")
    void sendText(String text);
    
    @RequestLine("POST /binary")
    @Headers("Content-Type: application/octet-stream")
    void sendBinary(byte[] data);
}

String Decoder Usage

import feign.codec.StringDecoder;

// Use string decoder for text responses
API api = Feign.builder()
    .decoder(new StringDecoder())
    .target(API.class, "https://api.example.com");

interface API {
    @RequestLine("GET /text-data")
    String getTextData();
}

Best Practices

Error Handling

  • Always handle encoding/decoding exceptions gracefully
  • Provide meaningful error messages in custom exceptions
  • Consider fallback behavior for unsupported types

Performance

  • Cache expensive resources (like JAXB contexts) in encoder/decoder implementations
  • Use streaming for large payloads when possible
  • Consider pooling encoder/decoder instances for high-throughput scenarios

Type Safety

  • Use generic types properly to maintain type safety
  • Validate input types in custom encoders/decoders
  • Handle edge cases like null values and empty responses

Testing

  • Test encoders/decoders independently with various input types
  • Mock HTTP responses for decoder testing
  • Verify correct Content-Type headers are set by encoders