Spring Boot + RabbitMQ (2021) - Error Handling Example | CodeUsingJava






















Spring Boot + RabbitMQ (2021) - Error Handling Example

In this tutorial we will be implementing a RabbitMQ Error Handling with the help of example.
Rabbitmq provides a way by which failure of message can be handled in a very smooth manner. This feature of the RabbitMQ is known as Retry and Error Handling feature. Whenever any data in the message is passed, that is not acceptable by the Receiver or there may be cases that Message is sent to the queue that is not present. The message is retried and sent again upto a certain attempts. Still if the message is not received by the receiver but sent from the sender side.
Now In such cases, the message queue is declared as undeliverable message queue or deadLetter queue.
The flow can be expressed in the following manner-
RabbitMqSenderErrorHandling

In this example, if the age passed in the RequestParam is not satisfying the condition, then the retry and error handling feature will come into picture and the message queue is declared as deadLetter qeue.

Project Structure

Sender

The sender is responsible for sending the message and put the message in the rabbitmq queue.
  • This will be the standard directory layout for maven project structure-
    Spring Boot Maven Project Structure
    We need to start by creating a Maven pom.xml(Project Object Model) file. The pom.xml file contains the project configuration details.
    	<?xml version="1.0" encoding="UTF-8"?>
    	<project xmlns="http://maven.apache.org/POM/4.0.0"
    		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    		<modelVersion>4.0.0</modelVersion>
    		<parent>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-parent</artifactId>
    			<version>2.5.1</version>
    			<relativePath /> <!-- lookup parent from repository -->
    		</parent>
    		<groupId>com.codeusingjava</groupId>
    		<artifactId>RabbitMqErrorHandling</artifactId>
    		<version>0.0.1-SNAPSHOT</version>
    		<name>RabbitMqErrorHandling</name>
    		<description>Spring boot Rabbitmq Error Handling</description>
    		<properties>
    			<java.version>1.8</java.version>
    		</properties>
    		<dependencies>
    	
    			
    			<dependency>
    				<groupId>org.projectlombok</groupId>
    				<artifactId>lombok</artifactId>
    				<version>1.18.20</version>
    				<scope>provided</scope>
    			</dependency>
    	
    	
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter</artifactId>
    			</dependency>
    	
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-test</artifactId>
    				<scope>test</scope>
    			</dependency>
    	
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-amqp</artifactId>
    			</dependency>
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-web</artifactId>
    			</dependency>
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-logging</artifactId>
    			</dependency>
    			<dependency>
    	
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-amqp</artifactId>
    			</dependency>
    	
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-web</artifactId>
    			</dependency>
    		</dependencies>
    	
    		<build>
    			<plugins>
    				<plugin>
    					<groupId>org.springframework.boot</groupId>
    					<artifactId>spring-boot-maven-plugin</artifactId>
    				</plugin>
    			</plugins>
    		</build>
    	
    	</project>
    	
    
    Create the model class for the student.
    We have used lombok dependency which generates the getter and setter only by adding dependency.
    	package com.codeusingjava.model;
    
    
    	import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    	import com.fasterxml.jackson.annotation.ObjectIdGenerators;
    	
    	import lombok.Getter;
    	import lombok.Setter;
    	import lombok.ToString;
    	
    	@Setter
    	@ToString
    	@Getter
    	@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id", scope = Student.class)
    	public class Student {
    	
    		private String name;
    		private String hobby;
    		private int age;
    	
    		}
    
    The configuration class for the sender can be defined in the following manner-
    For the configuration of a dead letter, we need to use x-dead-letter-exchange and x-dead-letter-routing-key. It tells the broker to use the default exchange.
    The binding of dlq is done with the deadLetterExchange and codeusingjava with the codeusingjavaExchange.
    The Jackson2JsonMessageConverter converts the object to JSON and then JSON to object of java.
    	package com.codeusingjava.config;
    
    	import org.springframework.amqp.core.AmqpTemplate;
    	import org.springframework.amqp.core.Binding;
    	import org.springframework.amqp.core.BindingBuilder;
    	import org.springframework.amqp.core.DirectExchange;
    	import org.springframework.amqp.core.Queue;
    	import org.springframework.amqp.core.QueueBuilder;
    	import org.springframework.amqp.rabbit.connection.ConnectionFactory;
    	import org.springframework.amqp.rabbit.core.RabbitTemplate;
    	import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
    	import org.springframework.amqp.support.converter.MessageConverter;
    	import org.springframework.context.annotation.Bean;
    	import org.springframework.context.annotation.Configuration;
    	
    	@Configuration
    	public class RabbitMQSenderConfig {
    	
    		@Bean
    		DirectExchange deadLetterExchange() {
    			return new DirectExchange("deadLetterExchange");
    		}
    		
    		@Bean
    		DirectExchange exchange() {
    			return new DirectExchange("codeusingjavaExchange");
    		}
    	
    		@Bean
    		Queue dlq() {
    			return QueueBuilder.durable("deadLetter.queue").build();
    		}
    	
    		@Bean
    		Queue queue() {
    			return QueueBuilder.durable("codeusingjava.queue").withArgument("x-dead-letter-exchange", "deadLetterExchange")
    					.withArgument("x-dead-letter-routing-key", "deadLetter").build();
    		}
    	
    		@Bean
    		Binding DLQbinding() {
    			return BindingBuilder.bind(dlq()).to(deadLetterExchange()).with("deadLetter");
    		}
    	
    		@Bean
    		Binding binding() {
    			return BindingBuilder.bind(queue()).to(exchange()).with("codeusingjava");
    		}
    	
    		@Bean
    		public MessageConverter jsonMessageConverter() {
    			return new Jackson2JsonMessageConverter();
    		}
    	
    		public AmqpTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
    			final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    			rabbitTemplate.setMessageConverter(jsonMessageConverter());
    			return rabbitTemplate;
    		}
    	}
    	
    
    The controller class for the Sender can be defined as follows-
    This class contains the mapping which will be executed for sending the message.
    The convertAndSend method first converts the java object to an amqp message and then this message is sent to the exchange using the routing key.
    	package com.codeusingjava.controller;
    
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    
    import com.codeusingjava.model.Student;
    
    @RestController
    
    public class RabbitMQSenderController {
    
    	@Autowired
    	private AmqpTemplate amqpTemplate;
    
    	@GetMapping(value = "/sendmsg")
    	public String producer(@RequestParam("name") String name,@RequestParam("hobby") String hobby,@RequestParam("age") int age) {
    		Student student=new Student();
    		student.setName(name);
    		student.setHobby(hobby);
    		student.setAge(age);
    
    		amqpTemplate.convertAndSend("codeusingjavaExchange", "codeusingjava", student);
    		return "Message sent to RabbitMQ server";
    	}
    }
    
    
    The main class for the Rabbitmq can bve defined as follows-
    	package com.codeusingjava;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class RabbitMqSenderErrorHandlingApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(RabbitMqSenderErrorHandlingApplication.class, args);
    	}
    
    }
    

    Receiver

    The Receiver class is the one who receives the message sent by the Sender. The project structure for the RabbitmqReceiver can be as follows-
    Spring Boot Maven Receiver Project Structure
    The pom.xml file for the Receiver class is as follows-
    	<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.5.1</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.codeusingjava</groupId>
    	<artifactId>RabbitMqErrorHandlingConsumer</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>RabbitMqErrorHandlingConsumer</name>
    	<description>Spring boot Rabbitmq Error Handling</description>
    	<properties>
    		<java.version>1.8</java.version>
    	</properties>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter</artifactId>
    		</dependency>
    		
    		
    		<dependency>
    			<groupId>org.projectlombok</groupId>
    			<artifactId>lombok</artifactId>
    			<scope>provided</scope>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-amqp</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-logging</artifactId>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
    

    The application.yml class is defined as follows-
    Here we have defined the max-attempts for retrying to send the message, initial as well as max-interval.
    After exceeding these limits, Exception is thrown and the message is declared as deadLetter queue.
    	spring:
      rabbitmq:
        listener:
          simple:
            retry:
              enabled: true
              initial-interval: 3s
              max-attempts: 6
              max-interval: 10s
              multiplier: 2
             
    server:
      port: 8081
    
    
    The model class Student can be defined as follows- @JsonIdentityInfo helps in serialising the instance of an object having the id of the object.
    package com.codeusingjava.model;
    
    import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    import com.fasterxml.jackson.annotation.ObjectIdGenerators;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Setter
    @ToString
    @Getter
    @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id", scope = Student.class)
    public class Student {
    
    	private String name;
    	private String hobby;
    	private int age;
    
    	}
    
    We define an exception class named as InvalidAgeException class.
    An exception will be thrown if any invalid age is passed in the parameter.
    	package com.codeusingjava.exception;
    
    	public class InvalidAgeException extends Exception {
    	
    		private static final long serialVersionUID = -3154618962130084535L;
    	
    	}
    
    The service class for the Receiver is as follows-
    It helps the message to filter on the basis of age of the Student. If the age of Student is less then 0 or greater then 25, then the message will be rejected.
    The RabbitListener listens the queue for any incoming message.
    	package com.codeusingjava.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    import com.codeusingjava.exception.InvalidAgeException;
    
    import com.codeusingjava.model.Student;
    
    @Component
    public class ReceiverService {
    
    	private static final Logger logger = LoggerFactory.getLogger(ReceiverService.class);
    
    	@RabbitListener(queues = "codeusingjava.queue")
    	public void recievedMessage(Student student) throws InvalidAgeException {
    		logger.info("Recieved Message From RabbitMQ: " + student);
    		if (student.getAge() < 0 || student.getAge() > 25 ) {
    			throw new InvalidAgeException();
    		}
    	}
    }
    
    The main class for the RabbitmqReceiver application is as follows-

    The Jackson2JsonMessageConverter converts the object to JSON and then JSON to object of java.
    	package com.codeusingjava;
    
    import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    @SpringBootApplication
    public class RabbitMqErrorHandlingReceiver {
    
    	public static void main(String[] args) {
    		SpringApplication.run(RabbitMqErrorHandlingReceiver.class, args);
    	}
    
    	
    	@Bean
    	public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
    		return new Jackson2JsonMessageConverter();
    	}
    }
    
    
Run the application
Firstly we need to run both the sender and the Receiver as Spring boot application. Run the folllowing url in the browser:-
http://localhost:8080/sendmsg?name=student1&hobby=dance&age=-5
As we can see, the age passed in the RequestParam is negative which is invalid and not acceptable by the Receiver.
Hence after several attempt, the InvalidAgeException is thrown and the message is declared as deadLetter queue.
The output displayed is as follows-
Browser output
Go to browser and type the following url-
http://localhost:15672/#/queues
the output would be as follows- The message declared as deadLetter in the rabbitmqconsole
Spring Boot Maven rabbitmqconsole
We can see at the spring boot console that the exception is thrown after several attempts of retrying.
Spring Boot Maven console output

Downloads-

Spring Boot + RabbitMQ Error Handling Example