SpringBootLearning (4 Part Series)
1 Open-Source Project: Banking Portal Rest API Using Spring Boot & Spring Security
2 Spring Boot Asynchronous OTP Generation and Email Sending
3 Spring Boot + MySQL + Spring Data JPA: A Beginner’s Guide to REST API CRUD Operations
4 Simplified Guide to JWT Authentication with Spring Boot
In modern web applications, security is of paramount importance. One-time passwords (OTPs) are widely used for secure user authentication and transaction verification. However, generating OTPs and sending them via email can be a time-consuming process, potentially affecting the responsiveness of your application. To address this, we will learn how to implement asynchronous OTP generation and email sending in a Spring Boot application.
By using CompletableFuture
and the @Async
annotation, we can achieve non-blocking behavior, allowing our application to handle other tasks while generating OTPs and sending emails in the background.
Prerequisites:
- Basic knowledge of Java and Spring Boot.
- A working Spring Boot project with a configured JavaMailSender for sending emails.
- An understanding of RESTful APIs and Spring controllers.
This is blog is part of this Banking Portal Api https://github.com/abhi9720/BankingPortal-API/
1. Project Setup: Before we begin, make sure you have set up a Spring Boot project and have a basic understanding of Spring Boot configurations.
2. Implementing Asynchronous OTP Generation: In this step, we will create a service class responsible for OTP generation and database operations.
Creating the OTPService Interface:
public interface OTPService {String generateOTP(String accountNumber);CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp);boolean validateOTP(String accountNumber, String otp);}public interface OTPService { String generateOTP(String accountNumber); CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp); boolean validateOTP(String accountNumber, String otp); }public interface OTPService { String generateOTP(String accountNumber); CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp); boolean validateOTP(String accountNumber, String otp); }
Enter fullscreen mode Exit fullscreen mode
Implementing the OTPServiceImpl Class:
@Servicepublic class OTPServiceImpl implements OTPService {@Autowiredprivate EmailService emailService;@Overridepublic String generateOTP(String accountNumber) {Random random = new Random();int otpValue = 100_000 + random.nextInt(900_000);String otp = String.valueOf(otpValue);// Save the new OTP information in the databaseOtpInfo otpInfo = new OtpInfo();otpInfo.setAccountNumber(accountNumber);otpInfo.setOtp(otp);otpInfo.setGeneratedAt(LocalDateTime.now());otpInfoRepository.save(otpInfo);return otp;}@Override@Asyncpublic CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp) {// Compose the email contentString subject = "OTP Verification";String emailText = emailService.getOtpLoginEmailTemplate(name, "xxx" + accountNumber.substring(3), otp);CompletableFuture<Void> emailSendingFuture = emailService.sendEmail(email, subject, emailText);return emailSendingFuture.thenApplyAsync(result -> true).exceptionally(ex -> {ex.printStackTrace();return false;});}// ... Other Code}@Service public class OTPServiceImpl implements OTPService { @Autowired private EmailService emailService; @Override public String generateOTP(String accountNumber) { Random random = new Random(); int otpValue = 100_000 + random.nextInt(900_000); String otp = String.valueOf(otpValue); // Save the new OTP information in the database OtpInfo otpInfo = new OtpInfo(); otpInfo.setAccountNumber(accountNumber); otpInfo.setOtp(otp); otpInfo.setGeneratedAt(LocalDateTime.now()); otpInfoRepository.save(otpInfo); return otp; } @Override @Async public CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp) { // Compose the email content String subject = "OTP Verification"; String emailText = emailService.getOtpLoginEmailTemplate(name, "xxx" + accountNumber.substring(3), otp); CompletableFuture<Void> emailSendingFuture = emailService.sendEmail(email, subject, emailText); return emailSendingFuture.thenApplyAsync(result -> true) .exceptionally(ex -> { ex.printStackTrace(); return false; }); } // ... Other Code }@Service public class OTPServiceImpl implements OTPService { @Autowired private EmailService emailService; @Override public String generateOTP(String accountNumber) { Random random = new Random(); int otpValue = 100_000 + random.nextInt(900_000); String otp = String.valueOf(otpValue); // Save the new OTP information in the database OtpInfo otpInfo = new OtpInfo(); otpInfo.setAccountNumber(accountNumber); otpInfo.setOtp(otp); otpInfo.setGeneratedAt(LocalDateTime.now()); otpInfoRepository.save(otpInfo); return otp; } @Override @Async public CompletableFuture<Boolean> sendOTPByEmail(String email, String name, String accountNumber, String otp) { // Compose the email content String subject = "OTP Verification"; String emailText = emailService.getOtpLoginEmailTemplate(name, "xxx" + accountNumber.substring(3), otp); CompletableFuture<Void> emailSendingFuture = emailService.sendEmail(email, subject, emailText); return emailSendingFuture.thenApplyAsync(result -> true) .exceptionally(ex -> { ex.printStackTrace(); return false; }); } // ... Other Code }
Enter fullscreen mode Exit fullscreen mode
Configuring Asynchronous Execution: Ensure that the @EnableAsync
annotation is present in your main application class to enable asynchronous processing.
@ SpringBootApplication@ EnableAsyncpublic class YourApplication {// Application setup and configuration code // ...}@ SpringBootApplication @ EnableAsync public class YourApplication { // Application setup and configuration code // ... }@ SpringBootApplication @ EnableAsync public class YourApplication { // Application setup and configuration code // ... }
Enter fullscreen mode Exit fullscreen mode
3. Sending OTP via Email Asynchronously: Now, let’s focus on the implementation of the EmailService responsible for sending emails.
Creating the EmailService Interface:public interface EmailService {public CompletableFuture<Void> sendEmail(String to, String subject, String text);public String getOtpLoginEmailTemplate(String name,String accountNumber, String otp) ;}Implementing the EmailServiceImpl Class:@Servicepublic class EmailServiceImpl implements EmailService {private final JavaMailSender mailSender;@Autowiredpublic EmailServiceImpl(JavaMailSender mailSender) {this.mailSender = mailSender;}@Override@Asyncpublic CompletableFuture<Void> sendEmail(String to, String subject, String text) {CompletableFuture<Void> future = new CompletableFuture<>();try {MimeMessage message = mailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message, true);helper.setTo(to);// No need to set the "from" address; it is automatically set by Spring Boot based on your propertieshelper.setSubject(subject);helper.setText(text, true); // Set the second parameter to true to send HTML contentmailSender.send(message);future.complete(null); // Indicate that the email sending is successful} catch (MessagingException e) {e.printStackTrace();future.completeExceptionally(e); // Indicate that the email sending failed}return future;}@Overridepublic String getOtpLoginEmailTemplate(String name, String accountNumber, String otp) {// Create the formatted email template with the provided valuesString emailTemplate = "<div style=\"font-family: Helvetica,Arial,sans-serif;min-width:1000px;overflow:auto;line-height:2\">"+ "<div style=\"margin:50px auto;width:70%;padding:20px 0\">"+ "<div style=\"border-bottom:1px solid #eee\">"+ "<a href=\"https://piggybank.netlify.app/\" style=\"font-size:1.4em;color: #00466a;text-decoration:none;font-weight:600\">piggybank</a>"+ "</div>"+ "<p style=\"font-size:1.1em\">Hi, " + name + "</p>"+ "<p style=\"font-size:0.9em;\">Account Number: " + accountNumber + "</p>"+ "<p>Thank you for choosing OneStopBank. Use the following OTP to complete your Log In procedures. OTP is valid for 5 minutes</p>"+ "<h2 style=\"background: #00466a;margin: 0 auto;width: max-content;padding: 0 10px;color: #fff;border-radius: 4px;\">" + otp + "</h2>"+ "<p style=\"font-size:0.9em;\">Regards,<br />OneStopBank</p>"+ "<hr style=\"border:none;border-top:1px solid #eee\" />"+ "<p>piggybank Inc</p>"+ "<p>1600 Amphitheatre Parkway</p>"+ "<p>California</p>"+ "</div>"+ "</div>";return emailTemplate;}}Creating the EmailService Interface: public interface EmailService { public CompletableFuture<Void> sendEmail(String to, String subject, String text); public String getOtpLoginEmailTemplate(String name,String accountNumber, String otp) ; } Implementing the EmailServiceImpl Class: @Service public class EmailServiceImpl implements EmailService { private final JavaMailSender mailSender; @Autowired public EmailServiceImpl(JavaMailSender mailSender) { this.mailSender = mailSender; } @Override @Async public CompletableFuture<Void> sendEmail(String to, String subject, String text) { CompletableFuture<Void> future = new CompletableFuture<>(); try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setTo(to); // No need to set the "from" address; it is automatically set by Spring Boot based on your properties helper.setSubject(subject); helper.setText(text, true); // Set the second parameter to true to send HTML content mailSender.send(message); future.complete(null); // Indicate that the email sending is successful } catch (MessagingException e) { e.printStackTrace(); future.completeExceptionally(e); // Indicate that the email sending failed } return future; } @Override public String getOtpLoginEmailTemplate(String name, String accountNumber, String otp) { // Create the formatted email template with the provided values String emailTemplate = "<div style=\"font-family: Helvetica,Arial,sans-serif;min-width:1000px;overflow:auto;line-height:2\">" + "<div style=\"margin:50px auto;width:70%;padding:20px 0\">" + "<div style=\"border-bottom:1px solid #eee\">" + "<a href=\"https://piggybank.netlify.app/\" style=\"font-size:1.4em;color: #00466a;text-decoration:none;font-weight:600\">piggybank</a>" + "</div>" + "<p style=\"font-size:1.1em\">Hi, " + name + "</p>" + "<p style=\"font-size:0.9em;\">Account Number: " + accountNumber + "</p>" + "<p>Thank you for choosing OneStopBank. Use the following OTP to complete your Log In procedures. OTP is valid for 5 minutes</p>" + "<h2 style=\"background: #00466a;margin: 0 auto;width: max-content;padding: 0 10px;color: #fff;border-radius: 4px;\">" + otp + "</h2>" + "<p style=\"font-size:0.9em;\">Regards,<br />OneStopBank</p>" + "<hr style=\"border:none;border-top:1px solid #eee\" />" + "<p>piggybank Inc</p>" + "<p>1600 Amphitheatre Parkway</p>" + "<p>California</p>" + "</div>" + "</div>"; return emailTemplate; } }Creating the EmailService Interface: public interface EmailService { public CompletableFuture<Void> sendEmail(String to, String subject, String text); public String getOtpLoginEmailTemplate(String name,String accountNumber, String otp) ; } Implementing the EmailServiceImpl Class: @Service public class EmailServiceImpl implements EmailService { private final JavaMailSender mailSender; @Autowired public EmailServiceImpl(JavaMailSender mailSender) { this.mailSender = mailSender; } @Override @Async public CompletableFuture<Void> sendEmail(String to, String subject, String text) { CompletableFuture<Void> future = new CompletableFuture<>(); try { MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setTo(to); // No need to set the "from" address; it is automatically set by Spring Boot based on your properties helper.setSubject(subject); helper.setText(text, true); // Set the second parameter to true to send HTML content mailSender.send(message); future.complete(null); // Indicate that the email sending is successful } catch (MessagingException e) { e.printStackTrace(); future.completeExceptionally(e); // Indicate that the email sending failed } return future; } @Override public String getOtpLoginEmailTemplate(String name, String accountNumber, String otp) { // Create the formatted email template with the provided values String emailTemplate = "<div style=\"font-family: Helvetica,Arial,sans-serif;min-width:1000px;overflow:auto;line-height:2\">" + "<div style=\"margin:50px auto;width:70%;padding:20px 0\">" + "<div style=\"border-bottom:1px solid #eee\">" + "<a href=\"https://piggybank.netlify.app/\" style=\"font-size:1.4em;color: #00466a;text-decoration:none;font-weight:600\">piggybank</a>" + "</div>" + "<p style=\"font-size:1.1em\">Hi, " + name + "</p>" + "<p style=\"font-size:0.9em;\">Account Number: " + accountNumber + "</p>" + "<p>Thank you for choosing OneStopBank. Use the following OTP to complete your Log In procedures. OTP is valid for 5 minutes</p>" + "<h2 style=\"background: #00466a;margin: 0 auto;width: max-content;padding: 0 10px;color: #fff;border-radius: 4px;\">" + otp + "</h2>" + "<p style=\"font-size:0.9em;\">Regards,<br />OneStopBank</p>" + "<hr style=\"border:none;border-top:1px solid #eee\" />" + "<p>piggybank Inc</p>" + "<p>1600 Amphitheatre Parkway</p>" + "<p>California</p>" + "</div>" + "</div>"; return emailTemplate; } }
Enter fullscreen mode Exit fullscreen mode
// application.propertiesspring.mail.host=smtp.gmail.comspring.mail.port=587spring.mail.username=example@gmail.comspring.mail.password=xxxxxxxxxxxxxxxxspring.mail.properties.mail.smtp.auth=truespring.mail.properties.mail.smtp.starttls.enable=true// application.properties spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=example@gmail.com spring.mail.password=xxxxxxxxxxxxxxxx spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true// application.properties spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=example@gmail.com spring.mail.password=xxxxxxxxxxxxxxxx spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true
Enter fullscreen mode Exit fullscreen mode
4. Creating the RESTful API: Now, let’s create a RESTful API to demonstrate the asynchronous OTP generation and email sending process.
// DTO For Receving OTP requestpublic class OtpRequest {private String accountNumber;public String getAccountNumber() {return accountNumber;}public void setAccountNumber(String accountNumber) {this.accountNumber = accountNumber;}}// DTO For Receving OTP request public class OtpRequest { private String accountNumber; public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } }// DTO For Receving OTP request public class OtpRequest { private String accountNumber; public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } }
Enter fullscreen mode Exit fullscreen mode
@RestControllerpublic class OTPController {@Autowiredprivate OTPService otpService;@PostMapping("/generate-otp")public ResponseEntity<?> generateOtp(@RequestBody OtpRequest otpRequest) {String accountNumber = otpRequest.getAccountNumber();// Fetch the user by account number to get the associated emailUser user = userService.getUserByAccountNumber(accountNumber);if (user == null) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User not found for the given account number");}// Generate OTP and save it in the databaseString otp = otpService.generateOTP(accountNumber);// Send the OTP to the user's email address asynchronouslyCompletableFuture<Boolean> emailSendingFuture = otpService.sendOTPByEmail(user.getEmail(), user.getName(), accountNumber, otp);// Wait for the email sending process to complete and handle the responsetry {boolean otpSent = emailSendingFuture.get(); // This will block until the email sending is completeif (otpSent) {// Return JSON response with success messagereturn ResponseEntity.ok().body("{\"message\": \"OTP sent successfully\"}");} else {// Return JSON response with error messagereturn ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}");}} catch (InterruptedException | ExecutionException e) {e.printStackTrace();// Return JSON response with error messagereturn ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}");}}}@RestController public class OTPController { @Autowired private OTPService otpService; @PostMapping("/generate-otp") public ResponseEntity<?> generateOtp(@RequestBody OtpRequest otpRequest) { String accountNumber = otpRequest.getAccountNumber(); // Fetch the user by account number to get the associated email User user = userService.getUserByAccountNumber(accountNumber); if (user == null) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User not found for the given account number"); } // Generate OTP and save it in the database String otp = otpService.generateOTP(accountNumber); // Send the OTP to the user's email address asynchronously CompletableFuture<Boolean> emailSendingFuture = otpService.sendOTPByEmail(user.getEmail(), user.getName(), accountNumber, otp); // Wait for the email sending process to complete and handle the response try { boolean otpSent = emailSendingFuture.get(); // This will block until the email sending is complete if (otpSent) { // Return JSON response with success message return ResponseEntity.ok().body("{\"message\": \"OTP sent successfully\"}"); } else { // Return JSON response with error message return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}"); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); // Return JSON response with error message return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}"); } } }@RestController public class OTPController { @Autowired private OTPService otpService; @PostMapping("/generate-otp") public ResponseEntity<?> generateOtp(@RequestBody OtpRequest otpRequest) { String accountNumber = otpRequest.getAccountNumber(); // Fetch the user by account number to get the associated email User user = userService.getUserByAccountNumber(accountNumber); if (user == null) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User not found for the given account number"); } // Generate OTP and save it in the database String otp = otpService.generateOTP(accountNumber); // Send the OTP to the user's email address asynchronously CompletableFuture<Boolean> emailSendingFuture = otpService.sendOTPByEmail(user.getEmail(), user.getName(), accountNumber, otp); // Wait for the email sending process to complete and handle the response try { boolean otpSent = emailSendingFuture.get(); // This will block until the email sending is complete if (otpSent) { // Return JSON response with success message return ResponseEntity.ok().body("{\"message\": \"OTP sent successfully\"}"); } else { // Return JSON response with error message return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}"); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); // Return JSON response with error message return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{\"message\": \"Failed to send OTP\"}"); } } }
Enter fullscreen mode Exit fullscreen mode
5. Testing the Asynchronous OTP Generation and Email Sending: Now that we have implemented the API, you can use tools like Postman to send a POST request to/generate-otp with the required data. The API will return a response indicating the status of the OTP generation and email sending.
For Any Reference Checkout this repo : https://github.com/abhi9720/BankingPortal-API/
SpringBootLearning (4 Part Series)
1 Open-Source Project: Banking Portal Rest API Using Spring Boot & Spring Security
2 Spring Boot Asynchronous OTP Generation and Email Sending
3 Spring Boot + MySQL + Spring Data JPA: A Beginner’s Guide to REST API CRUD Operations
4 Simplified Guide to JWT Authentication with Spring Boot
原文链接:Spring Boot Asynchronous OTP Generation and Email Sending
暂无评论内容