In this post, I will show how we can use the annotation @ControllerAdvice – Controller Advice – an exception handler in the Spring Boot application. If you want to read how to handle uncaught exceptions in Spring Boot, you can check my old post.
What is @ControllerAdvice ?
Spring 3.2 introduced an annotation @ControllerAdvice. The annotation allows the handling of exceptions across the application. Before this, Spring offered another annotation @ExceptionHandler for exception handling. But, you have to add this annotation in each controller class of your application. It doesn’t help on the application level.
@ControllerAdvice is an annotation-driven interceptor. It intercepts most of those classes that include @RequestMapping.
Comparison with @ExceptionHandler
In most controller classes, you can add @ExceptionHandler annotation to handle exceptions for that class. Nevertheless, in such classes, one can add an extra method to handle exceptions thrown by @RequestMapping methods in the same controller. These exception handling methods can redirect the user to the error page OR build a custom error response.
This will look like below:
@RestController
@RequestMapping("/companies")
public class CompanyController
{
@GetMapping
public List<Company> getAllCompanies(HttpServletRequest req) throws Exception {
}
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
Additionally, you can see the method getAllCompanies throw an Exception. The method handleError will handle the exception thrown by getAllCompanies.
Furthermore, if I have another controller like UserController, I will end up writing like below if it has to handle exceptions.
@RestController
@RequestMapping("/users")
public class UserController
{
@GetMapping
public List<User> getAllUsers(HttpServletRequest req) throws Exception {
}
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
Henceforth, this makes a lot of duplicate code. This is where Controller Advice comes into the picture with an advantage.
Example of Controller Advice
A Controller Advice allows you to use the same exception handling technique across applications, without repeating any code.
Consequently, a class annotated with @ControllerAdvice implements three types of methods:
Exception handling method annotated with @ExceptionHandler
Model enhancement methods annotated with @ModelAttribute
Binder initialization methods annotated with @InitBinder
@ControllerAdvice
public class GlobalExceptionHandler
{
@ExceptionHandler(CompanyNotFoundException.class)
public ModelAndView handleError(HttpServletRequest req, CompanyNotFoundException ex)
{
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity handleUserExceptionError(HttpServletRequest req, HttpServletResponse res, UserNotFoundException ex)
{
List errors = Collections.singletonList(ex.getMessage());
// Get headers
if(HttpStatus.INTERNAL_SERVER_ERROR.equals(res.getStatus()))
{
// do something
}
return new ResponseEntity<>(new ApiError(errors), headers, status);
}
}
This will allow now to intercept any exception thrown from controllers. This makes implementing exception handling easier.
Conclusion
In this post, I showed how we can implement Controller Advice – exception handler in Spring Boot. This is a very effective way to handle exceptions in the current applications built with Spring Boot.
In this post, I will explain how to use AWS Sagemaker. Even if you do not have experience with this AWS service, this guide will help understand AWS Sagemaker step by step.
What is AWS Sagemaker?
AWS Sagemaker is a new web service that AWS offers. It helps to build, train and deploy machine learning models at any scale. Basically, Sagemaker does the heavy lifting of machine learning and as a developer or data scientist, you can focus on building and training your model.
Major Benefits of AWS Sagemaker
You can easily fetch or store data from other AWS Services
Highly Scalable. This again relates to my earlier point by being able to connect to other AWS Services.
Does heavy lifting of ML algorithms – Fast training
Details Of Machine Learning and Sagemaker
Machine learning is literally machine learning about something. Nevertheless, that something can be anything that humans are usually good at or bad at. Machine Learning provides an ability for systems to learn and improve from experience.
In another way, you can say a system with a feedback loop. A system performs functions, gathers data along the way, uses that data to improve the functions it is performing.
Building a model
Sagemaker makes it easy to connect with AWS Services like S3, Database. Sagemaker also includes Juypter notebooks. These notebooks make it easier to visualize data.
Sagemaker also offers a set of algorithms pre-installed. Sagemaker also comes in with preconfigured TensorFlow or Apache MXNet.
Training and Deploying a model
I will show later in this post how we can train a model in Sagemaker with a single click. The important thing to note here is that you can easily train a model for petabyte-scale in Sagemaker. With continuous improvement, Sagemaker can also improve the performance of the model.
Once you train and tune the model in Sagemaker, it is easy to deploy the model in production. Sagemaker deploys the model on an auto-scaling cluster of EC2 instances.
A simple example of using AWS Sagemaker
Once, you log in to the AWS console, access Sagemaker service. Select Notebook Instances and create a Jupyter Notebook instance as shown below:
2. On the next page, keep the most default settings as shown. You will need to create an IAM role for S3 bucket creation. If you don’t have that role, you can create it while selecting the role.
3. Once you select the role, click “create a notebook instance” and it will create a notebook instance. It will take few minutes before it will show it is running. Once the notebook instance is running, click “open” and it will open Jupyter notebook in another tab.
4. Select notebook environment as conda_python3 or anything that you want to use.
Once you have the notebook opened, you can use python or the language of your choice to build a model. For the model, you can easily fetch data from S3 or relational databases from AWS service.
I will not be showing that part in this post. But if you want to refer to a good example, you can visit this post here.
Conclusion
In this post, I showed how one can use AWS Sagemaker to build and train the model for machine learning.
In this post, I will show how we can use the API Gateway pattern with Spring Cloud. With microservice architecture becoming more and more useful, it has become equally complex how to handle calls to the microservices.
The purpose of microservices is to decouple the application into loosely coupled microservices that can interact with clients and with each other easily.
Importantly, the ease of development and deployment make microservices easier to design based on specific needs.
API Gateway Design Pattern
When the enterprise architecture scales, it becomes complicated with the number of microservices. Clients can directly call these microservices, but there are a few challenges
Each client has to make a request to the exposed microservice API. In many cases, it might have to make multiple server round trips. As a result of this, it increases network latency.
Every microservice has to implement common functionalities like authentication, logging, and authorization.
It becomes harder to change microservice without affecting the client. In reality, the client doesn’t need to know microservice and its implementation behind.
To address these issues, the architecture now contains another layer between the client and the microservices. This is API Gateway.
API Gateway acts like a proxy that routes the request to the appropriate microservices and returns a response to the client. Microservices can also interact with each other through this Gateway.
Usage of API Gateway
There are a few functionalities that API Gateway provides.
Routing
The major usage of API Gateway is routing the request from the client to the appropriate server or microservice. Particularly, API Gateway hides the implementation of API from the client.
Common Functionalities
API Gateway can also implement extra common functionalities and in-process reducing the load from microservices. These common functionalities include logging, authentication, authorization, load balancing, response caching, retry policies, circuit breakers, rate limiter.
Different API Gateways
There are a number of API Gateways available and one can use any of these based on the needs.
Overall, which API Gateway to use will depend on your use case. But the most of these gateways provide options to scale, flexibility and support.
In this demo, I will be showing how to use spring-cloud-starter-netflix-zuul library for Netflix API Gateway.
Example of API Gateway with Spring Cloud
However, we will develop two microservices. We will also build an API Gateway using Spring Cloud. This API Gateway will act as a reverse proxy to route to either of the microservices.
So let’s create the first microservice. This will contain a CustomerController like below:
@RestController
@RequestMapping("/customer")
public class CustomerController
{
@GetMapping("/total")
public List customers()
{
List list = new ArrayList<>();
list.add("Microsoft");
list.add("Amazon");
list.add("Apple");
return list;
}
}
This microservice will be running on port 8081. server.port=8081.
Now, let’s create another microservice. This will contain VendorController like below:
@RestController
@RequestMapping("/vendor")
public class VendorController
{
@GetMapping("/total")
public List vendors()
{
List list = new ArrayList<>();
list.add("CJI Consultants");
list.add("Signature Consultants");
list.add("Deloitte");
return list;
}
}
This microservice will be running on port 8082. server.port=8082
API Gateway with Spring Cloud
After all, we will create an API Gateway using Spring Cloud. We need to include the following dependencies:
Note the dependency of spring-cloud-starter-gateway. Nevertheless, we will need a RouteLocator type bean to route our requests. This is where we add the configuration in our Api Gateway.
As shown above, this configuration bean builds a RouteLocator to route requests related to two modules. Also, note that our gateway service is running at port 8080. If a request is initiated with a gateway address, the API gateway will route it to the appropriate service.
Demo
Let’s start out our microservices and API Gateway service. Two microservices are running on ports 8081 and 8082. The API gateway service is running on port 8080.
Now if I access http://localhost:8080/vendor/total, I will get the list of vendors as follows:
If I access http://localhost:8080/customer/total, I will get the list of customers as follows:
Conclusion
Conclusively, I showed how to use API Gateway with Spring Cloud. API Gateway is an important design concept. With an increasing number of microservices, it becomes important to have a common pattern that can handle a lot of the common workload of these services, and API Gateway helps with that.
AWS Cognito is a web service from AWS. Cognito is a user directory as well as an authentication mechanism service. In the enterprise industry, every application has two requirements from a user perspective.
User Directory and Synchronization
User Authentication
Cognito makes this easier by allowing the creation of a user pool or an identity pool. Once you have a pool, you can configure an application with the various settings for authentication. Another major advantage of Cognito is that it offers industry-standard security authentication protocols like OAuth 2.0, OpenID Connect, SAML.
There are equally other features like user access handling, but we won’t go into detail about that as part of this post. In this post, I will further show how we can create a user pool and configure an application to use that user pool.
Spring Boot Application
We will use a simple Spring Boot Application. We will integrate this application with AWS Cognito for authentication.
@Controller
public class MainController
{
@GetMapping("/")
public String home(Model model, Principal principal)
{
model.addAttribute("username", principal.getName());
return "index";
}
}
So when we will access our application, it will redirect to AWS Cognito UI for authentication. Based on the selected flow OIDC or SAML, the authentication will happen.
Once you log in to AWS Console, select Cognito as AWS Service. The first screen will show you two options – Create a user pool and Create an identity pool.
We will select Create a user pool.
Enter a suitable name for your user pool and select Step through settings. This will allow us to modify any settings that we don’t want to use for our use case.
Now select user pool attributes for users. These attributes allow us to identify the user and what a user can enter during authentication.
For our demo, we have selected Email address, family name, and given name as attributes. So the user will log in to the application using an email address.
For password policy, we have set that password should be of minimum length 8, should be alphanumeric with upper case and lower case characters.
Most of the other settings we will choose as default. For App Clients Step, we will choose username and password-based authentication for Auth Flow.
Application Client Configuration
Once we create a user pool, we will continue to step through the next set of configurations and that includes the Application Client. In this configuration, we configure our application that will be using the user pool.
We need to enable identity provider and Cognito User Pool.
Configuring Federation Identity Provider
As mentioned previously, we will also configure a SAML Identity Provider for authentication. In this case, the flow will be application -> AWS Cognito -> SAML Identity Provider.
Configure Application in Okta
For this demo, we will use Okta as SAML Identity Provider. Log in to Okta Administrator console , configure a new application for SAML Integration as below:
On the next step, provide Single Sign On URL and this will be https://{yourcustomdomainfromcognito}.auth.{awszone}.amazoncognito.com. We also need to provide Audience URI (SP Entity ID). In this case, Cognito will act as Service Provider to Okta.
We also need to configure Attribute Statements as shown above. Those are the attributes we have configured in our Cognito User Attributes – email, given_name, family_name.
Basically, make sure that the user you plan to use is in Okta Users Directory. It will be the same user that we will need to be either configured in Cognito User Pool or created through sign-up.
Configure Federated Identity Provider in Cognito
In our user pool configuration, select Federated Identity Provider.
Choose the option of SAML and Cognito will show you to upload a metadata file or metadata URL for Identity Provider. In our case, it will be https://dev-19753289.okta.com/app/exkmt322q1Kl15Rsk5d6/sso/saml/metadata.
Type a name for Federated Identity Provider and Save the changes.
Therefore, if we go back to App Client Settings, we should see an option to select Federated Identity Provider. With the new configuration, our settings will look like below:
Anyhow, make sure the user attributes(given_name, family_name, and email) in Cognito are editable.
However, we have completed our configuration. Now we can show the demo.
Demo of Spring Boot Application Authentication with AWS Cognito
Now if I run my application and access it at https://localhost:8743/, I will see the following screen to select an identity provider to authenticate with:
Nevertheless, we can use Okta or Cognito User Pool to log in.
SAML Flow
User accesses the application that sends Authorization Code Flow OAuth request to Cognito
Cognito sends SAML Request to Okta.
The user enters credentials on the okta login screen.
Okta sends the SAML response back to Cognito at endpoint https://{customdomain}.auth.{awszone}.amazoncognito.com/saml2/idpresponse
Cognito processes the saml response and generates auth code and response back to the application.
The application uses auth code to get token from Cognito and authenticates the user on verification of token.
Above all, after successful authentication, the user will see
Instead of Okta, we had chosen Cognito login on the same Home Realm Discovery screen, we will see the following after successful authentication:
Conclusion
In this post, I showed how we can use AWS Cognito for authentication with Spring Boot application. Moreover, Cognito helps in configuring users as well as removing boilerplate code of security flows.
In conclusion, if you have not bought my ebook Simplifying Spring Security, it is available here.
In this post, I will show the comparison of the two retries – Spring Retry vs Resilience4j Retry. Usually, you can combine retry with a circuit breaker when implementing to make your application more robust. I already covered the circuit breaker demo. Also, I have updated my book Simplifying Spring Security with Okta Demo if you are interested to learn more about Spring Security.
Spring Retry vs Resilience4j Retry
Spring Retry allows applications to retry a failed operation automatically. In most cases, if your service is calling another service and another service is not responding for some reason, you can use Spring Retry to retry the same operation. This provides another way to make your service more available.
Retry makes your application more robust and less prone to failures. You can either configure Spring Retry on a method that you think can fail or you can configure a RetryTemplate. The ease of configuration makes Spring Retry an easier choice when writing code.
On other hand, the Resilience4j Retry module offers an equally easier configuration – either through code or through properties.
In this post, I will show how to use Spring Retry and Resilience4j Retry modules when calling any methods or services.
When to use Retry?
Usually, you should consider Retry operation in certain scenarios.
HTTP call to a REST Endpoint
Sending or retrieving messages from SQS
Remote Procedure call or a web service
Fetching or storing data from databases
In such cases, we can either throw an error if we fail to do the operation successfully. But with the availability of applications becoming more important, most of the time, these errors are trivial and most services come back online within a few milliseconds to seconds.
Therefore, it makes sense to apply retry. You must be careful that the operation that you are applying retry with must be idempotent. Suppose, your application sent a request and the target service received the request, but in between something happened and your target service couldn’t respond in time. Then, with retry, the target service should not treat the retry attempt as a separate or new request. This makes your system more resilient.
Spring Retry
In this section, I will show various ways to use Spring Retry. To start with, we will have a simple Spring Boot REST application to retrieve a list of companies from the database. As usual, I will not show how to build a Spring Boot application.
Gradle Dependencies
To use Spring Retry, we need two dependencies in our configuration.
Once, we have spring-retry dependency, we will be able to annotate our main class with annotation @EnableRetry as follows:
package com.betterjavacode.retrydemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
@SpringBootApplication
@EnableRetry
@EnableJpaRepositories(basePackages = "com.betterjavacode.retrydemo.daos")
public class RetrydemoApplication {
public static void main(String[] args) {
SpringApplication.run(RetrydemoApplication.class, args);
}
@Bean
public RetryTemplate retryTemplate()
{
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(100);
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
I will explain the rest of the code as we go along, but just note here the annotation @EnableRetry. This will enable the retry in our application.
REST Controller
We will show Spring Retry in two different ways.
Using @Retryable annotation
Using RetryTemplate
Our REST Controller will fetch us a list of companies, a company by id, or a list of companies by name. It will look like below:
package com.betterjavacode.retrydemo.controllers;
import com.betterjavacode.retrydemo.dtos.CompanyDto;
import com.betterjavacode.retrydemo.service.CompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/v1/betterjavacode/companies")
public class CompanyController
{
@Autowired
CompanyService companyService;
@GetMapping
public ResponseEntity<List> getAllCompanies()
{
List companyDtos = companyService.getAllCompanies();
if(companyDtos.isEmpty())
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDtos, HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity getCompanyById(@PathVariable("id") long id)
{
CompanyDto companyDto = companyService.getCompany(id);
if(companyDto == null)
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDto, HttpStatus.OK);
}
@GetMapping("/")
public ResponseEntity<List> searchCompanies(@RequestParam("name") String companyName)
{
List companyDtos = companyService.searchCompanyByName(companyName);
if(companyDtos.isEmpty())
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(companyDtos, HttpStatus.OK);
}
}
In our controller, we are using a @Service object called CompanyService. This service object provides us with a way to implement our methods to fetch company data.
Service with various Retry Configuration
So, we will see how we can use annotation @Retryable:
@Retryable(value = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
public List getAllCompanies()
{
List companies = companyRepository.findAll();
List companyDtos = new ArrayList<>();
for(Company company : companies)
{
CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(), company.getState(), company.getDescription());
companyDtos.add(companyDto);
}
return companyDtos;
}
In the above code, we are fetching a list of companies. If this method fails to fetch the result with any exception related to SQLException, we will retry the fetching. We will retry this twice as configured with maxAttempts. Between each attempt, there will be a delay of 100 milliseconds. Now if we run our application and call this method, we will see how this retry works.
To simulate the error, I will stop SQL Service from Windows Services. I will show a successful response and a retried response below:
As you can see in the above screenshot, there were two attempts to retry. In each retry, it tried to connect to MySQL server thrice.
What is Spring Boot Retry Template?
Similarly, we can also use retry template that Spring-Retry offers. In the following code, I show a method that I have added in CompanyService to get company data for an id.
public CompanyDto getCompany(long id)
{
CompanyDto companyDto = retryTemplate.execute(rt -> {
Company company = companyRepository.findById(id).get();
CompanyDto localCompanyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(),
company.getState(), company.getDescription());
return localCompanyDto;
});
return companyDto;
}
This retryTemplate bean is configured with simpleRetryPolicy with 2 attempts and 100 milliseconds delay between each attempt. Nevertheless, if I try to execute this method the same way I did for @Retryable, we will see the below output:
As mentioned above, all I am doing is stopping my MySQL service from windows services and it allows my method to get executed to retry.
Is Retry Template Thread Safe?
Retry Template class is thread-safe. It allows concurrent access. In return, one can execute multiple operations.
Resilience4j Retry
While using resilience4j-retry library, you can register a custom global RetryConfig with a RetryRegistry builder. Use this registry to build a Retry. In our demo to fetch company data, we added a new method to retrieve companies by name.
This method will look like below:
public List searchCompanyByName(String name)
{
LOGGER.info("Search for company = {}", name);
RetryConfig retryConfig =
RetryConfig.custom().maxAttempts(4).waitDuration(Duration.of(2, SECONDS)).build();
RetryRegistry retryRegistry = RetryRegistry.of(retryConfig);
Retry retryConfiguration = retryRegistry.retry("companySearchService", retryConfig);
Supplier<List> companiesSupplier = () -> companyRepository.findAllByName(name);
Supplier<List> retryingCompaniesSearch =
Retry.decorateSupplier(retryConfiguration, companiesSupplier);
List companyDtos = new ArrayList<>();
List companies = retryingCompaniesSearch.get();
LOGGER.info("Retrying..");
for(Company company : companies)
{
CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
company.getCity(), company.getState(), company.getDescription());
companyDtos.add(companyDto);
}
return companyDtos;
}
In the above method, we first create RetryConfig. We create a RetryRegistry and add RetryConfig in this registry. Then when we create our call to fetch a list of companies. We decorate this call with retryConfiguration.
Customizations with Resilience4j-Retry
RetryConfig offers different customization:
maxAttempts – 3 is the default number of attempts for retries.
waitDuration – a fixed wait duration between each retry attempt.
intervalFunction – a function to modify the waiting interval after a failure.
retryOnResultPredicate – configures a predicate that evaluates if a result should be retried.
retryExceptions – Configures a list of throwable classes that are used for retrying
ignoreExceptions – Configures a list of throwable classes that are ignored
failAfterMaxRetries – A boolean to enable or disable throwing of MaxRetriesExceededException when the Retry has reached the configured maxAttempts
Demo
Now, let’s look at what happens when we execute this method with resilience4j-retry. The following screenshot shows the successful response when SQL service is still running.
If I stop SQL service, we will see the retry attempts 4 times as we have configured it for 4.
In this post, I showed the comparison between Spring Retry vs Resilience4j Retry. When to use either of these libraries depends on your scenario. Usually, Resilience4j Retry goes well if you also plan to resilience4j circuit breaker module. Spring Retry can be handy with various configurations as well using RetryTemplate.
If you enjoyed this post, please subscribe to my blog here.