{"id":14568,"date":"2016-02-29T20:57:57","date_gmt":"2016-02-29T19:57:57","guid":{"rendered":"https:\/\/blog.trifork.com\/?p=14568"},"modified":"2016-02-29T20:57:57","modified_gmt":"2016-02-29T19:57:57","slug":"spring-amqp-payload-validation","status":"publish","type":"post","link":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/","title":{"rendered":"Spring-AMQP and payload validation: some notes from the trenches"},"content":{"rendered":"<p><span style=\"font-weight: 400\">It\u2019s been a while since I\u2019ve written one of our from-the-trenches blogs: that\u2019s mostly because I\u2019ve been very busy in those trenches developing systems for our customers.<\/span><\/p>\n<p><span style=\"font-weight: 400\">This week I completed a Spring Boot-based microservice which is responsible for interacting with some 3rd party SOAP service: its own clients communicate with it by sending request message over RabbitMQ, and the service then sends back a response to a response queue after handling the SOAP response message.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Of course I used Spring-AMQP to build this service. Spring-AMQP supports a nice annotation-based listener programming model, based on Spring\u2019s generic Message support.<br \/>\n<\/span><span style=\"font-weight: 400\">That allows writing listener methods like this:<\/span><\/p>\n<pre class=\"brush: java; light: true; title: ; notranslate\" title=\"\">@RabbitListener(queues = REQUEST_QUEUE)\npublic DeclarationResponse submitDeclaration(DeclarationRequest request) {\n  \/\/ handle the request and return a response\n}<\/pre>\n<p><span style=\"font-weight: 400\">The request parameter here is the result of converting the incoming AMQP message using a Spring-AMQP MessageConverter, after which it is considered to be the payload of the message (even when headers are used in the conversion as well).<\/span><\/p>\n<p><span style=\"font-weight: 400\">The request messages that the clients send have some required fields: without those fields, the service can\u2019t make the SOAP calls. While reading the RabbitListener JavaDoc I noticed that Spring-AMQP allows you to apply validation to message payload parameters by annotating it. When using this, you also have to add the <\/span><span style=\"font-weight: 400\">@Payload<\/span><span style=\"font-weight: 400\"> annotation (which is optional without validation if your method doesn\u2019t have any other arguments), so the result looks like this:<\/span><\/p>\n<pre class=\"brush: java; light: true; title: ; notranslate\" title=\"\">@RabbitListener(queues = REQUEST_QUEUE)\npublic DeclarationResponse submitDeclaration(@Valid @Payload DeclarationRequest request) { \u2026 }<\/pre>\n<p><span style=\"font-weight: 400\">By the way, Spring\u2019s own <\/span><span style=\"font-weight: 400\">@Validated<\/span><span style=\"font-weight: 400\"> (even as a meta-annotation) and in fact <em>every<\/em> annotation whose name starts with \u201cValid\u201d are supported for this purpose as well. <\/span><\/p>\n<p><span style=\"font-weight: 400\">Now we can add some JSR-303 Bean Validation annotations to the fields in our DeclarationRequest, like @NotNull, to express our validation constraints.<\/span><\/p>\n<p><!--more--><\/p>\n<h2><span style=\"font-weight: 400\">Defining and registering a validator<\/span><\/h2>\n<p><span style=\"font-weight: 400\">You might think, as I did at first, that this would suffice to have the parameter validated before the listener method is invoked. However, as it turns out you have to explicitly inject a Validator into a DefaultMessageHandlerMethodFactory which you then have to register as the MessageHandlerMethodFactory to be used by Spring-AMQP <a href=\"http:\/\/docs.spring.io\/spring-amqp\/reference\/html\/_reference.html#async-annotation-driven-enable-signature\" target=\"_blank\" rel=\"noopener\">per the documentation<\/a>, even when using the Spring Boot autoconfiguration<\/span><\/p>\n<p><span style=\"font-weight: 400\">To do that, you need a Validator bean. Since I\u2019m using a Spring Boot web application, there should already be a default Validator bean &#8212;\u00a0created by\u00a0the web auto-configuration &#8212; called \u201cmvcValidator\u201d. However, when I tried to autowire that Validator into my configuration I got an unexpected error at startup:<\/span><\/p>\n<pre class=\"brush: plain; light: true; title: ; notranslate\" title=\"\">ERROR 10732 --- &#x5B; main] o.s.boot.SpringApplication : Application startup failed\n\norg.springframework.beans.factory.BeanCreationException:\n  Error creating bean with name 'defaultServletHandlerMapping'\n  defined in class path resource\n  &#x5B;org\/springframework\/boot\/autoconfigure\/web\/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:\n  Bean instantiation via factory method failed; nested exception is\n  org.springframework.beans.BeanInstantiationException:\n  Failed to instantiate &#x5B;org.springframework.web.servlet.HandlerMapping]:\n  Factory method 'defaultServletHandlerMapping' threw exception;\n  nested exception is java.lang.IllegalArgumentException:\n   A ServletContext is required to configure default servlet handling\n at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod\n at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod\n&#x5B;...]\n at org.springframework.boot.SpringApplication.run\n at myapp.Application.main\nCaused by: org.springframework.beans.BeanInstantiationException:\n Failed to instantiate &#x5B;org.springframework.web.servlet.HandlerMapping]:\nFactory method 'defaultServletHandlerMapping' threw exception;\nnested exception is java.lang.IllegalArgumentException:\n  A ServletContext is required to configure default servlet handling\n at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate\n at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod\n ... 18 common frames omitted\nCaused by: java.lang.IllegalArgumentException:\n  A ServletContext is required to configure default servlet handling\n at org.springframework.util.Assert.notNull\n at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.&lt;init&gt;;\n at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping\n&#x5B;...]\n ... 19 common frames omitted<\/pre>\n<p>Oops&#8230;<\/p>\n<p><span style=\"font-weight: 400\">Apparently adding an \u201c@Autowired Validator validator\u201d to your main Spring Boot class triggers some premature initialization causing this error.<br \/>\n<\/span><span style=\"font-weight: 400\">Interestingly enough, when you move your AMQP-related Spring configuration (I had everything in my main class first) to a dedicated @Configuration class and autowire the Validator into that, this problem does not happen.<\/span><\/p>\n<p><span style=\"font-weight: 400\">I\u2019m not even going to try to understand the details of that; Spring initialization in Boot is a rather complicated and obviously sometimes also brittle process, so to err on the safe side I simply added a dedicated Validator for Spring-AMQP like this:<\/span><\/p>\n<pre class=\"brush: java; light: true; title: ; notranslate\" title=\"\">@Override\npublic void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {\n   registrar.setMessageHandlerMethodFactory(validatingHandlerMethodFactory());\n}\n\n@Bean\nDefaultMessageHandlerMethodFactory validatingHandlerMethodFactory() {\n   DefaultMessageHandlerMethodFactory factory =\n       new DefaultMessageHandlerMethodFactory();\n   factory.setValidator(amqpValidator());\n   return factory;\n}\n\n@Bean\nValidator amqpValidator() {\n   return new OptionalValidatorFactoryBean();\n}<\/pre>\n<h2><span style=\"font-weight: 400\">Handling validation errors<\/span><\/h2>\n<p><span style=\"font-weight: 400\">So, with this in place we now have working payload validation for incoming AMQP-messages!<br \/>\n<\/span><span style=\"font-weight: 400\">Hooray, let\u2019s check what happens if we now send an invalid message:<\/span><\/p>\n<pre class=\"brush: plain; light: true; title: ; notranslate\" title=\"\">WARN 22764 --- &#x5B;cTaskExecutor-1] s.a.r.l.ConditionalRejectingErrorHandler :\nExecution of Rabbit message listener failed.\n\norg.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException:\nListener method could not be invoked with the incoming message\nEndpoint handler details:\nMethod &#x5B;public myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest)]\nBean &#x5B;myapp.WsClient@e8e0dec]\n at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:120)\n&#x5B;...]\nCaused by: org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException:\nCould not resolve method parameter at index 0 in method:\npublic myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest) ,\nwith 3 error(s): &#x5B;Field error in object 'request' on field 'declarationId': rejected value &#x5B;null]; &#x5B;...]\n at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.validate(PayloadArgumentResolver.java:178)\n&#x5B;...]\n ... 12 common frames omitted\n\nWARN 22764 --- &#x5B;cTaskExecutor-1] s.a.r.l.ConditionalRejectingErrorHandler :\nExecution of Rabbit message listener failed.\n\norg.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException:\nListener method could not be invoked with the incoming message\nEndpoint handler details:\nMethod &#x5B;public myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest)]\nBean &#x5B;myapp.WsClient@e8e0dec]\n at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:120)\n&#x5B;...]\nCaused by: org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException:\nCould not resolve method parameter at index 0 in method:\npublic myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest) ,\nwith 3 error(s): &#x5B;Field error in object 'request' on field 'declarationId': rejected value &#x5B;null]; &#x5B;...]\n at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.validate(PayloadArgumentResolver.java:178)\n&#x5B;...]\n ... 12 common frames omitted\n\nWARN 22764 --- &#x5B;cTaskExecutor-1] s.a.r.l.ConditionalRejectingErrorHandler :\nExecution of Rabbit message listener failed.\n\norg.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException:\nListener method could not be invoked with the incoming message\nEndpoint handler details:\nMethod &#x5B;public myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest)]\nBean &#x5B;myapp.WsClient@e8e0dec]\n at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:120)\n&#x5B;...]\nCaused by: org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException:\nCould not resolve method parameter at index 0 in method:\npublic myapp.DeclarationResponse myapp.WsClient.submitDeclaration(myapp.DeclarationRequest) ,\nwith 3 error(s): &#x5B;Field error in object 'request' on field 'declarationId': rejected value &#x5B;null]; &#x5B;...]\n at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.validate(PayloadArgumentResolver.java:178)\n&#x5B;...]\n ... 12 common frames omitted\n<\/pre>\n<p><em>[repeated ad infinitum]<br \/>\n<\/em><br \/>\n<span style=\"font-weight: 400\">What you see here is that when the message is received and the validation fails, the message is rejected and put back on the queue. However, that only causes the message to be received again, and of course the redelivered message will fail to validate again, causing the same error over and over again. <\/span><\/p>\n<p><span style=\"font-weight: 400\">I found this quite surprising, as I had expected a validation error to be considered as fatal (it&#8217;s non-transient in nature), thus causing the message to simply be discarded instead of requeued. Instead, it becomes a \u2018poison pill\u2019 which causes this pathological behavior.<br \/>\n<\/span><span style=\"font-weight: 400\">BTW, in the case of AMQP a message is put back at the <em>tail<\/em> of the queue when it\u2019s being requeued: that will at least not prevent other messages from being received. With JMS, where an unacknowledged message will be put back at the <em>head<\/em> of the queue, this would be even worse: it would prevent the processing of any further messages!<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Discarding invalid\u00a0messages<\/span><\/h2>\n<p><span style=\"font-weight: 400\">After a little digging in the Spring-AMQP sources, I found that you remedy this relatively easily: the SimpleRabbitListenerContainerFactory (which is the only supplied implementation of a RabbitListenerContainerFactory) creates a SimpleMessageListenerContainer with a default ErrorHandler of type ConditionalRejectingErrorHandler<\/span><span style=\"font-weight: 400\">. This ConditionalRejectingErrorHandler uses a so-called FatalExceptionStrategy to determine if an exception should be considered \u2018fatal\u2019, i.e. if it should cause the received message to be discarded rather than requeud.<br \/>\n<\/span><span style=\"font-weight: 400\">The standard strategy only considers an exception to be fatal if it was caused by a MessageConversionException. However, when there is a validation error then this doesn\u2019t cause a MessageConversionException, but a MethodArgumentNotValidException. Therefore, you need to implement your own FatalExceptionStrategy that considers that root exception as fatal too. Here\u2019s how you can implement and configure that in your configuration class:<\/span><\/p>\n<pre class=\"brush: java; light: true; title: ; notranslate\" title=\"\">@Bean\nSimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {\n  SimpleRabbitListenerContainerFactory listenerContainerFactory =\n      new SimpleRabbitListenerContainerFactory();\n  listenerContainerFactory.setConnectionFactory(connectionFactory);\n  listenerContainerFactory.setErrorHandler(\n      new ConditionalRejectingErrorHandler(\n          new InvalidPayloadRejectingFatalExceptionStrategy()));\n  listenerContainerFactory.setMessageConverter(messageConverter());\n  return listenerContainerFactory;\n}\n\n\/**\n * Extension of Spring-AMQP's\n * {@link ConditionalRejectingErrorHandler.DefaultExceptionStrategy}\n * which also considers a root cause of {@link MethodArgumentNotValidException}\n * (thrown when payload does not validate) as fatal.\n *\/\nstatic class InvalidPayloadRejectingFatalExceptionStrategy implements FatalExceptionStrategy {\n\n  private Logger logger = LoggerFactory.getLogger(getClass());\n\n  @Override\n  public boolean isFatal(Throwable t) {\n    if (t instanceof ListenerExecutionFailedException &amp;&amp;\n          (t.getCause() instanceof MessageConversionException ||\n           t.getCause() instanceof MethodArgumentNotValidException)) {\n      logger.warn(&quot;Fatal message conversion error; message rejected; it will be dropped: {}&quot;,\n                  ((ListenerExecutionFailedException) t).getFailedMessage());\n      return true;\n    }\n    return false;\n  }\n}<\/pre>\n<p><span style=\"font-weight: 400\">This implementation differs from Spring-AMQP\u2019s default in two ways: most importantly, it checks for MethodArgumentNotValidExceptions in addition to MessageConversionExceptions. Also, it does not log a stack trace when returning true: that stack trace will already have been logged when this code is invoked by the ConditionalRejectingErrorHandler, so there\u2019s no need to log it again.<br \/>\n<\/span><span style=\"font-weight: 400\">I\u2019d argue that including\u00a0MethodArgumentNotValidExceptions here\u00a0should be the default implementation really, so I\u2019m planning to file an issue for that.<\/span><\/p>\n<p><span style=\"font-weight: 400\">And that\u2019s it, then: we now have validation in place that will cause invalid messages to simply be dropped when received after logging the problem in detail (but only once). <\/span><\/p>\n<p><span style=\"font-weight: 400\">Alternatively, you could simply disable requeueing of rejected messages altogether on your SimpleRabbitListenerContainerFactory:<\/span><\/p>\n<pre class=\"brush: java; light: true; title: ; notranslate\" title=\"\">listenerContainerFactory.setDefaultRequeueRejected(false);<\/pre>\n<p><span style=\"font-weight: 400\">However, that would cause message loss if some transient error occurrs while processing the message: in my particular case that\u2019s undesirable.<\/span><\/p>\n<h2>Conclusion<\/h2>\n<p><span style=\"font-weight: 400\">I hope that post will help some people who start out with validating their AMQP messages and run into the same issues as I did.<br \/>\n<\/span><span style=\"font-weight: 400\">This is another great example of why it\u2019s so nice to use good quality open-source software for your applications: it allows you to redefine the behavior of the libraries in an easily controlled manner, and allows you to propose improvements to the libraries themselves as well!<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>It\u2019s been a while since I\u2019ve written one of our from-the-trenches blogs: that\u2019s mostly because I\u2019ve been very busy in those trenches developing systems for our customers. This week I completed a Spring Boot-based microservice which is responsible for interacting with some 3rd party SOAP service: its own clients communicate with it by sending request [&hellip;]<\/p>\n","protected":false},"author":62,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[337,10,94],"tags":[419,420,70,164],"class_list":["post-14568","post","type-post","status-publish","format-standard","hentry","category-from-the-trenches","category-development","category-spring","tag-amqp","tag-rabbitmq","tag-spring","tag-validation"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog\" \/>\n<meta property=\"og:description\" content=\"It\u2019s been a while since I\u2019ve written one of our from-the-trenches blogs: that\u2019s mostly because I\u2019ve been very busy in those trenches developing systems for our customers. This week I completed a Spring Boot-based microservice which is responsible for interacting with some 3rd party SOAP service: its own clients communicate with it by sending request [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/\" \/>\n<meta property=\"og:site_name\" content=\"Trifork Blog\" \/>\n<meta property=\"article:published_time\" content=\"2016-02-29T19:57:57+00:00\" \/>\n<meta name=\"author\" content=\"Joris Kuipers\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Joris Kuipers\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/\",\"url\":\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/\",\"name\":\"Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog\",\"isPartOf\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#website\"},\"datePublished\":\"2016-02-29T19:57:57+00:00\",\"author\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/265bd41e503f7176742258a927de598b\"},\"breadcrumb\":{\"@id\":\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/trifork.nl\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Spring-AMQP and payload validation: some notes from the trenches\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/trifork.nl\/blog\/#website\",\"url\":\"https:\/\/trifork.nl\/blog\/\",\"name\":\"Trifork Blog\",\"description\":\"Keep updated on the technical solutions Trifork is working on!\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/trifork.nl\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/265bd41e503f7176742258a927de598b\",\"name\":\"Joris Kuipers\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/9ab8da0d60582bad84342d4602d23dbd?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/9ab8da0d60582bad84342d4602d23dbd?s=96&d=mm&r=g\",\"caption\":\"Joris Kuipers\"},\"sameAs\":[\"http:\/\/www.trifork.nl\"],\"url\":\"https:\/\/trifork.nl\/blog\/author\/jorisk\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/","og_locale":"en_US","og_type":"article","og_title":"Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog","og_description":"It\u2019s been a while since I\u2019ve written one of our from-the-trenches blogs: that\u2019s mostly because I\u2019ve been very busy in those trenches developing systems for our customers. This week I completed a Spring Boot-based microservice which is responsible for interacting with some 3rd party SOAP service: its own clients communicate with it by sending request [&hellip;]","og_url":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/","og_site_name":"Trifork Blog","article_published_time":"2016-02-29T19:57:57+00:00","author":"Joris Kuipers","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Joris Kuipers","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/","url":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/","name":"Spring-AMQP and payload validation: some notes from the trenches - Trifork Blog","isPartOf":{"@id":"https:\/\/trifork.nl\/blog\/#website"},"datePublished":"2016-02-29T19:57:57+00:00","author":{"@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/265bd41e503f7176742258a927de598b"},"breadcrumb":{"@id":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/trifork.nl\/blog\/spring-amqp-payload-validation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/trifork.nl\/blog\/"},{"@type":"ListItem","position":2,"name":"Spring-AMQP and payload validation: some notes from the trenches"}]},{"@type":"WebSite","@id":"https:\/\/trifork.nl\/blog\/#website","url":"https:\/\/trifork.nl\/blog\/","name":"Trifork Blog","description":"Keep updated on the technical solutions Trifork is working on!","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/trifork.nl\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/265bd41e503f7176742258a927de598b","name":"Joris Kuipers","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/9ab8da0d60582bad84342d4602d23dbd?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/9ab8da0d60582bad84342d4602d23dbd?s=96&d=mm&r=g","caption":"Joris Kuipers"},"sameAs":["http:\/\/www.trifork.nl"],"url":"https:\/\/trifork.nl\/blog\/author\/jorisk\/"}]}},"_links":{"self":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/14568","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/users\/62"}],"replies":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/comments?post=14568"}],"version-history":[{"count":0,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/14568\/revisions"}],"wp:attachment":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media?parent=14568"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/categories?post=14568"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/tags?post=14568"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}