I have a code like:
public class BigDecimalDeserializer extends JsonDeserializer<BigDecimal> {
private static final Pattern PATTERN = Pattern
.compile("^([1-9]+[0-9]*)((\\.)[0-9]+)?$");
private static final String MESSAGE = "must be a number in format (99 or 99.99)";
private static final String EMPTY_STRING = "";
#Override
public BigDecimal deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException
{
BigDecimal result = null;
JsonNode node = parser.getCodec().readTree(parser);
String text = node.asText();
if (text != null && !text.equals(EMPTY_STRING)) {
Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
result = new BigDecimal(text);
} else {
throw new ApplicationException(MESSAGE);
}
}
return result;
}
}
Nothing happens when this method throws ApplicationException or other type of exception.
Where this exception catches? And why there is no stacktraces in the console?
p.s. im trying to handle this exception in the #ControllerAdvice class (#ExceptionHandler(ApplicationException.class) method)..but nothing happens.
MessageConverter config:
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
ContollerAdvice:
#ExceptionHandler(ApplicationException.class)
protected ResponseEntity<Object> handleWrongRequest(
RuntimeException exception, WebRequest request) {
ApplicationException applicationException = (ApplicationException) exception;
Error error = new Error();
error.setField(EMPTY);
error.setMessage(applicationException.getRootMessage());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exception, error, headers,
HttpStatus.FORBIDDEN, request);
}
Where Spring handles Jackson exceptions?
Related
In my spring boot application I've used a RestTemplateInterceptor to log request and response details in debug mode. To mask the sensitive information in request payload and response body, I've created a custom annotation #LogMaskedStringValue and annotated some fields in request DTO and response DTO. I've created a Serializer MaskStringSerializer to mask the annotated fields with the help of object mapper.
I tried to set the request payload type and expected response body type in request headers and I'm retrieving it in interceptor. But it is not the legitimate way to do, cause the header dependency prevents to use this interceptor in other applications, I tried using RestTemplateRequestCustomizer , Unfortunately it didn't work. Is there any way to get the request payload type and response body type in RestTemplateInterceptor ?
```
#Slf4j
public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor {
private final LogDetailsStorage logDetailsStorage;
private final static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public RestTemplateLoggingInterceptor(LogDetailsStorage logDetailsStorage, String message) {
this.logDetailsStorage = logDetailsStorage;
this.message = message;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
logDetailsStorage.setOutboundStartTime(System.currentTimeMillis());
String requestType = request.getHeaders().getFirst("requestType");
String responseType = request.getHeaders().getFirst("responseType");
request.getHeaders().remove("requestType");
request.getHeaders().remove("responseType");
logRequest(request, body, requestType);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response, responseType);
return response;
}
private void logRequest(HttpRequest request, byte[] body, String requestType) {
if (log.isDebugEnabled()) {
logDetailsStorage.setOutboundRequestUrl(request.getURI().toString());
logDetailsStorage.setOutboundRequestMethod(request.getMethodValue());
MDC.put(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey(), logDetailsStorage.getOutboundRequestMethod());
MDC.put(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey(), logDetailsStorage.getOutboundRequestUrl());
if (body != null && body.length > 0) {
String requestPayload = new String(body, StandardCharsets.UTF_8);
logDetailsStorage.setOutboundRequestPayload(getMaskedPayload(requestType, requestPayload));
MDC.put(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundRequestPayload());
}
log.debug("Making request for " + logDetailsStorage.getOutboundRequestUrl());
MDC.remove(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey());
}
}
private void logResponse(ClientHttpResponse response, String responseType) throws IOException {
if (log.isDebugEnabled()) {
String responsePayload = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset());
logDetailsStorage.setOutboundResponsePayload(getMaskedPayload(responseType, responsePayload));
logDetailsStorage.setOutboundStatusCode(String.valueOf(response.getRawStatusCode()));
logDetailsStorage.setOutboundExecutionTime((System.currentTimeMillis() - logDetailsStorage.getOutboundStartTime()) / 1000d + " seconds");
MDC.put(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey(), logDetailsStorage.getOutboundRequestMethod());
MDC.put(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey(), logDetailsStorage.getOutboundRequestUrl());
MDC.put(MdcKey.OUTBOUND_RESPONSE_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundResponsePayload());
MDC.put(MdcKey.OUTBOUND_STATUS_CODE.getMdcKey(), logDetailsStorage.getOutboundStatusCode());
if (logDetailsStorage.getOutboundRequestPayload() != null) {
MDC.put(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundRequestPayload());
}
MDC.put(MdcKey.OUTBOUND_EXECUTION_TIME.getMdcKey(), logDetailsStorage.getOutboundExecutionTime());
log.debug("Got Response for "+ logDetailsStorage.getOutboundRequestUrl());
MDC.remove(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_EXECUTION_TIME.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_STATUS_CODE.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_RESPONSE_PAYLOAD.getMdcKey());
}
}
private String getMaskedPayload(String classType, String payload) {
if (!StringUtils.isEmpty(classType)) {
try {
Object obj = objectMapper.readValue(payload, Class.forName(classType));
payload = LogUtil.getObjectAsMaskedJsonString(obj);
} catch (JsonProcessingException e) {
log.error("'Failed to parse the payload : {}", payload, e);
} catch (ClassNotFoundException e) {
log.error("Class not found exception occurred : {}", classType, e);
}
}
else {
log.warn("ClassType is empty during getMaskedPayload : {}", classType);
}
return payload;
}
}
```
```
public class MaskStringSerializer extends StdSerializer<String> implements ContextualSerializer {
private String mask;
public MaskStringSerializer() {
super(String.class);
}
public MaskStringSerializer(String mask) {
super(String.class);
this.mask = mask;
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Optional<String> maskValue = Optional.ofNullable(property)
.map(p -> p.getAnnotation(LogMaskStringValue.class))
.map(LogMaskStringValue::value);
return maskValue.map(MaskStringSerializer::new).orElseGet(MaskStringSerializer::new);
}
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (mask != null) {
gen.writeString(mask);
} else {
gen.writeString(Optional.ofNullable(value).orElse("null"));
}
}
}
```
```
#UtilityClass
#Slf4j
public class LogUtil {
private final static ObjectMapper sensitiveMapper = new ObjectMapper();
static {
SimpleModule module = new SimpleModule();
module.addSerializer(new MaskStringSerializer());
sensitiveMapper.registerModule(module);
}
public static String getObjectAsMaskedJsonString(Object object) {
String requestBody;
try {
requestBody = sensitiveMapper.writeValueAsString(object);
} catch (JsonProcessingException jsonProcessingException) {
log.error("Error while parsing object: {}", object, jsonProcessingException);
requestBody = object.toString();
}
return requestBody;
}
}
```
```
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Card {
#LogMaskStringValue
private String id;
private String type;
private String last4;
private Integer expirationMonth;
private Integer expirationYear;
}
```
```
`
I have a global exception handler as below :-
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
#ExceptionHandler(value= {HttpMessageNotReadableException.class})
public final ResponseEntity<ErrorDetails> validationException(HttpMessageNotReadableException ex, WebRequest request) {
System.out.println("This was called.");
if(ex.getCause() instanceof CsvRequiredFieldEmptyException){
CsvRequiredFieldEmptyException csvExp = (CsvRequiredFieldEmptyException) ex.getCause();
String exceptionDtls = csvExp.getMessage().concat(" ").concat(" at line number "+csvExp.getLineNumber()+ " in the csv filw.");
ErrorDetails errorDetails = new ErrorDetails(LocalDate.now(),exceptionDtls, request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}
I am invoking the rest API by using TestRestTemplate for integration testing.
ResponseEntity<?> response = restTemplate.exchange(ITestUtils.createURLWithPort(postUrlCsv,
host,port ), HttpMethod.POST,listingDocEnt, String.class);
#Test
public void uploadListingCsvTest_Returns400BadReq_WhenCodeMissing() throws HttpMessageNotReadableException {
// Step 1 : Create the Http entity object which contains the request body and headers.
HttpEntity<ListingList> listingDocEnt = new HttpEntity<ListingList>(createTestDataForNewVehicleListingCodeMissing(),
getHttpHeaderCsv());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
List<HttpMessageConverter<?>> csvMessgeonverter = new ArrayList<>();
csvMessgeonverter.add(new CsvHttpMessageConverter<>());
csvMessgeonverter.add(converter);
TestRestTemplate restTemplate = new TestRestTemplate();
restTemplate.getRestTemplate().setMessageConverters(csvMessgeonverter);
ResponseEntity<?> response = restTemplate.exchange(ITestUtils.createURLWithPort(postUrlCsv,
host,port ), HttpMethod.POST,listingDocEnt, String.class);
// Check if the response is not null and the http status code is - 201 Created.
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.BAD_REQUEST,response.getStatusCode());
}
My rest API has custom HttpMessageConverter which is as below which converts the input request csv to java object in the rest controller. This custom message converter has a method readInternal which throws an exception HttpMessageNotReadableException , but still the exception handler method 'validationException' is not getting invoked. The Junit simply breaks and fails.
public class CsvHttpMessageConverter<T, L extends ListParam<T>>
extends AbstractHttpMessageConverter<L> {
public CsvHttpMessageConverter () {
super(new MediaType("text", "csv"));
}
#Override
protected boolean supports (Class<?> clazz) {
return ListParam.class.isAssignableFrom(clazz);
}
#Override
protected L readInternal (Class<? extends L> clazz,HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
Class<T> t = toBeanType(clazz.getGenericSuperclass());
strategy.setType(t);
CSVReader csv = new CSVReader(new InputStreamReader(inputMessage.getBody()));
CsvToBean<T> csvToBean = new CsvToBean<>();
List<T> beanList = null;
try {
beanList = csvToBean.parse(strategy, csv);
} catch(Exception exception){
throw new HttpMessageNotReadableException("Exception while parsing the CSV file.",exception.getCause());
}
try {
L l = clazz.newInstance();
l.setList(beanList);
return l;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#SuppressWarnings("unchecked")
#Override
protected void writeInternal (L l, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
strategy.setType(toBeanType(l.getClass().getGenericSuperclass()));
OutputStreamWriter outputStream = new OutputStreamWriter(outputMessage.getBody());
StatefulBeanToCsv<T> beanToCsv =
new StatefulBeanToCsvBuilder(outputStream)
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.withMappingStrategy(strategy)
.build();
try {
beanToCsv.write(l.getList());
} catch (CsvDataTypeMismatchException e) {
throw new HttpMessageNotWritableException("Exception while parsing the CSV file.",e);
} catch (CsvRequiredFieldEmptyException e) {
throw new HttpMessageNotWritableException("Exception while parsing the CSV file.",e);
}
outputStream.close();
}
#SuppressWarnings("unchecked")
private Class<T> toBeanType (Type type) {
return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
}
Is there a way that when invoking a spring rest API using TestRestTemplate we can invoke the exception handler method when there is an exception?
I think the problem here is that the HttpMessageNotReadableException is not thrown by the Controller, but instead by the spring infrastructure BEFORE the controller is invoked.
But a #ControllerAdvice does only handle exceptions that are thrown by the Controller.
In spring's DispatcherServlet there is also an ErrorHandler that is invoked in such cases. Maybe this is a solution for you?
Here are some infos about this:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#going-deeper
I am creating a async rest call using spring
#GetMapping(path = "/testingAsync")
public String value() throws ExecutionException, InterruptedException, TimeoutException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
String baseUrl = "https://api.github.com/users/XXX";
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String value = "";
HttpEntity entity = new HttpEntity("parameters", requestHeaders);
ListenableFuture<ResponseEntity<User>> futureEntity = restTemplate.getForEntity(baseUrl, User.class);
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() {
#Override
public void onSuccess(ResponseEntity<User> result) {
System.out.println(result.getBody().getName());
// instead of this how can i return the value to the user ?
}
#Override
public void onFailure(Throwable ex) {
}
});
return "DONE"; // instead of done i want to return value to the user comming from the rest call
}
And is there any way i can convert ListenableFuture to use CompletableFuture that is used in java 8 ?
There are basically 2 things you can do.
Remove the ListenableFutureCallback and simply return the ListenableFuture
Create a DeferredResult and set the value of that in a ListenableFutureCallback.
Returning a ListenableFuture
#GetMapping(path = "/testingAsync")
public ListenableFuture<ResponseEntity<User>> value() throws ExecutionException, InterruptedException, TimeoutException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
String baseUrl = "https://api.github.com/users/XXX";
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String value = "";
HttpEntity entity = new HttpEntity("parameters", requestHeaders);
return restTemplate.getForEntity(baseUrl, User.class);
}
Spring MVC will add a ListenableFutureCallback itself to fill a DeferredResult and you will get a User eventually.
Using a DeferredResult
If you want more control on what to return you can use a DeferredResult and set the value yourself.
#GetMapping(path = "/testingAsync")
public DeferredResult<String> value() throws ExecutionException, InterruptedException, TimeoutException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
String baseUrl = "https://api.github.com/users/XXX";
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String value = "";
HttpEntity entity = new HttpEntity("parameters", requestHeaders);
final DeferredResult<String> result = new DeferredResult<>();
ListenableFuture<ResponseEntity<User>> futureEntity = restTemplate.getForEntity(baseUrl, User.class);
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() {
#Override
public void onSuccess(ResponseEntity<User> result) {
System.out.println(result.getBody().getName());
result.setResult(result.getBody().getName());
}
#Override
public void onFailure(Throwable ex) {
result.setErrorResult(ex.getMessage());
}
});
return result;
}
I don't know too much about async calls in Spring but I would imagine that you could return the text that you want through the ResponseBody
It would look like this:
#GetMapping(path = "/testingAsync")
#ResponseBody
public String value() throws ExecutionException, InterruptedException, TimeoutException {
...
...
#Override
public void onSuccess(ResponseEntity<User> result) {
return result.getBody().getName();
}
...
}
Sorry if this isn't what you are asking about.
i want to add my Custom MappingJackson2HttpMessageConverter to Spring Boot . it set successful as Converter but did not use it for converting ...
i see this error just for spring 4.3 and upper. it successful set in spring 4.0.3
How do i correct this converter ???
here is my code
public class ResponseViewEntity<T> extends
ResponseEntity<ContainerViewEntity<T>> {
private Class<? extends View.Base> view;
public ResponseViewEntity(HttpStatus statusCode) {
super(statusCode);
}
public ResponseViewEntity(T body, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, View.Base.class), statusCode);
}
public ResponseViewEntity(T body, Class<? extends View.Base> view, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, view), statusCode);
}
Converter :
public class JsonViewMessageConverter extends
MappingJackson2HttpMessageConverter {
private ObjectMapper objectMapper = new HibernateAwareObjectMapper();
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(clazz);
try {
return objectMapper.readValue(inputMessage.getBody(), javaType);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: "
+ ex.getMessage(), (Throwable) ex);
}
}
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ContainerViewEntity
&& ((ContainerViewEntity) object).hasView()) {
writeView((ContainerViewEntity) object, outputMessage);
} else {
super.writeInternal(object, outputMessage);
}
}
protected void writeView(ContainerViewEntity view,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders()
.getContentType());
ObjectWriter writer = getWriterForView(view.getView());
JsonGenerator jsonGenerator = writer.getFactory().createGenerator(
outputMessage.getBody(), encoding);
try {
writer.writeValue(jsonGenerator, view.getObject());
} catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: "
+ ex.getMessage(), (Throwable) ex);
}
}
private ObjectWriter getWriterForView(Class<?> view) {
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
return objectMapper.writer().withView(view);
}
protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
protected JsonEncoding getJsonEncoding(MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
Charset charset = contentType.getCharset();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (!charset.name().equals(encoding.getJavaName()))
continue;
return encoding;
}
}
return JsonEncoding.UTF8;
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull((Object) objectMapper,
(String) "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
and My config
#Bean
public JsonViewMessageConverter mappingJackson2HttpMessageConverter() {
JsonViewMessageConverter jsonConverter = new JsonViewMessageConverter();
ObjectMapper objectMapper = new HibernateAwareObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
}
The webservice returns an empty string instead of NULL which causes Jackson to crash.
So I created a custom parser, and I'm trying to parse it manually? Any idea How I could achieve this?
What Am I doing wrong here? All I'm trying to do is to parse JSON to object as I normally would. The field names are added to my properties using #JsonProperty so the parser should know how to convert it.
public class InsertReplyDeserializer extends JsonDeserializer<ListingReply> {
#Override
public ListingReply deserialize(JsonParser jsonParser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
// If service returns "" instead of null return a NULL object and don't try to parse
if (node.getValueAsText() == "")
return null;
ObjectMapper objectMapper = new ObjectMapper();
ListingReply listingReply = objectMapper.readValue(node, ListingReply.class);
return listingReply;
}
}
Here is how I resolved it
#Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
if (node.getValueAsText() == "")
return null;
MyObject myObject = new MyObject();
myObject.setMyStirng(node.get("myString").getTextValue());
JsonNode childNode = node.get("childObject");
ObjectMapper objectMapper = new ObjectMapper();
ChildObject childObject = objectMapper.readValue(childNode,
ChildObject.class);
myObject.setChildObject(childObject);
return myObject;
}
I am not sure you need to manually parse response. You solution would work but seems sub-optimal in my opinion. Since it looks like that you are using RestTemplate, you should rather write (or move your parser code to) your own message converter. Then add this converter to your rest template object which will internally deserialize the value for you. Something along the lines,
public class CustomHttpmsgConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
InputStream istream = inputMessage.getBody();
String responseString = IOUtils.toString(istream);
if(responseString.isEmpty()) //if your response is empty
return null;
JavaType javaType = getJavaType(clazz);
try {
return this.objectMapper.readValue(responseString, javaType);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(responseString);
}
}
//add this converter to your resttemplate
RestTemplate template = new RestTemplate();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new CustomHttpmsgConverter());
template.setMessageConverters(converters);