Exception Handling Interview Questions in Java & Spring

Exception Handling Interview Questions in Java & Spring | Also see:- Exception Handling in Java, Develop User-defined Custom exception, Java Custom Exception Example, Exception Handling in Spring Boot REST

The exception is an abnormal condition that occurs during the execution of a program and disrupts the normal flow of the program. If not handled properly it can cause the program to terminate abruptly.

Using try, catch block.

  • Try:- Encloses a set of statements that can throw exceptions and hence are required to be monitored.
  • Catch:- When an exception occurs, this block catches that exception and works accordingly to handle it or to throw it as required.
  • Finally:- This block always gets executed regardless of exception occurrence. Hence clean up is done here. 
checked vs unchecked exception

The java.lang.Throwable:- It is the root class of the exception hierarchy. It has two main branches:

  1. Error:- These are serious problems that applications should not try to catch. Subclasses:-
    • StackOverflowError
    • OutOfMemoryError
    • VirtualMachineError
  2. Exception
    • Checked exceptions: Must be declared or handled.
      • IOException
        • E.g., FileNotFoundException, EOFException
      • SQLException
      • ClassNotFoundException
      • InvocationTargetException
      • InterruptedException
      • NoSuchMethodException
      • CloneNotSupportedException
    • RuntimeException: These are unchecked exceptions and do not need to be declared or handled. Subclasses:-
      • NullPointerException
      • ArrayIndexOutOfBoundsException
      • StringIndexOutOfBoundsException
      • NegativeArraySizeException
      • ArithmeticException
      • ClassCastException
      • IllegalArgumentException
        • E.g., NumberFormatException
      • IllegalStateException

Both Errors and Exceptions in Java represent abnormal conditions, but errors are usually indicative of serious problems at the system level, while exceptions are typically recoverable and can be handled by application code. Errors are instances of the `Error` class or its subclasses, while exceptions are instances of the `Exception` class or its subclasses, including `RuntimeException` and its subclasses.

ExceptionError
We can recover from exceptions using a try-catch block or using throw.Recovering from Error is not possible.
The compiler will have knowledge about checked Exceptions hence Compiler will force you to use try-catch blocks.The compiler will not have any knowledge about unchecked exceptions and Errors
Exceptions are related to the applicationErrors are related to the environment where the application is running 
Exceptions include both checked as well as unchecked types.All errors in Java are unchecked type.
Exceptions in Java are of type java.lang.Exception.Errors in java are of type java.lang.Error.

No. either catch or finally is a must.

Compile time error saying “insert finally to complete try statement”.

No. Try must be followed directly by either catch or finally.

No. If an exception occurs at a particular point in the try block then all statements after that statement where the exception occurred will not be executed and the flow goes directly to the catch block (if there is any) else the program terminates. Hence we need the finally block to do all the cleaning up like closing files or removing locks.

In Java, throw and throws are both related to exception handling, but they serve different purposes:

1. throw:-

  • The throw keyword is used to explicitly throw an exception within a method or a block of code.
  • When we use throw, we are raising an exception manually, typically when some error condition occurs that cannot be handled within the normal flow of execution.
  • We can throw any subclass of Throwable, including Exception or Error, and even custom exception classes that you define.

Example:

public void someMethod() {
   if (errorCondition) {
       throw new SomeException("Error message");
   }
}

2. throws:-

  • The `throws` keyword is used in method declarations to indicate that the method may throw certain types of exceptions during its execution.
  • When we use `throws` in a method signature, we are essentially declaring that the method might not handle the exception itself but will pass it on to the caller.
  • It specifies the exceptions that can be thrown by the method but doesn’t throw any exceptions itself.

Example:-

public void someMethod() throws SomeException {
    // Method code that may throw SomeException
}

Multiple exceptions can be declared using a comma-separated list:-

public void someMethod() throws ExceptionType1, ExceptionType2 {
    // Method code that may throw ExceptionType1 or ExceptionType2
}

In summary:-

  • throw is used to raise an exception explicitly within a method or block of code.
  • throws is used in method declarations to indicate which exceptions the method may throw, leaving it to the caller to handle or propagate them further.
throwthrows
The Java “throw” keyword is used to explicitly throw an exception.Java “throws” keyword is used to declare an exception.
Checked exceptions cannot be propagated using throw only.Checked exceptions can be propagated with throws.
Throw is used within the method.Throws is used with the method signature.
You cannot throw multiple exceptions.You can declare multiple exceptions.

When an exception is thrown by the main() method, Java Runtime terminates the program and prints the exception message and the stack trace in the system console. Since main() is the entry point and doesn’t have a caller method therefore exception propagation doesn’t happen.

This error comes when you keep superclasses first and subclasses later. Like below, we kept Exception which is the parent of NullPointerException.

try {
    System.out.println("Test.main()");
} catch(Exception e) {
    System.out.println(e.getMessage());
} catch(NullPointerException npe) { // error: unreachable catch block
    System.out.println(npe.getMessage());
}

Hence the order of catch blocks must be from the most specific to the most general ones. 

To reduce code duplication and make it easier to maintain, Java 7 came up with this multi-catch block concept.

Here are the catch block arguments that have different expectations piped. Example:-

try {
   // ....
} catch(NullPointerException | SQLException ex) {
   // ....
}
  • final:- It is a keyword used to apply restriction on the class, method, and variable. The final class can’t be inherited. The final method can not be overridden and the final variable can not be changed after initialization.
  • finally:- This keyword is used with the try-catch block to provide statements that will always get executed even if some exception arises. Usually, finally is used to close resources.
  • finalize:- It performs clean-up processing just before the object is garbage collected.
checked vs unchecked exception
CheckedUnchecked Exceptions
Another NameChecked exceptions are checked by the Java compiler so they are called compile-time exceptions.Unchecked exceptions are not checked by the compiler. These are called runtime exceptions.
WhenChecked exceptions occur at compile time.Unchecked exceptions occur at runtime.
Handled byThese types of exceptions can be handled at the time of compilation.These types of exceptions cannot be caught or handled at the time of compilation, because they get generated by the mistakes in the program.
HandlingMust be handled in a try-and-catch block, or be thrown by the invoking methodException handling semantics are not mandatory.
ForceJava compiler forces us to handle these exceptions in some mannerA method is not forced by the compiler to declare the unchecked exceptions into the method declaration.
HierarchyThey are all subclasses of Exception.They are all subclasses of RuntimeException.
ScenarioThe checked exception represents a scenario with a higher failure rateUnchecked Exceptions are mostly programming mistakes.
Throws clauseThe throws clause on a method header must be included for checked exceptions that are not caught and handled in the method.The throws clause on a method header is not mandatory
ExamplesFileNotFoundException
NoSuchFieldException
InterruptedException
NoSuchMethodException
ClassNotFoundException
NoSuchElementException
EmptyStackException
ArithmeticException
NullPointerException
ArrayIndexOutOfBoundsException

Global Exception Handling in Spring Boot

In real-world projects, It’s very important to handle errors correctly and simultaneously provide meaningful error messages to the clients too. 

  • Spring already comes with built-in support for error handling. 
  • It’s our job to understand and implement it. 

In spring boot for global exception handling, we need the following annotations – 

  • @ControllerAdvice: The @ControllerAdvice annotation handles exceptions globally. It allows you to use the same ExceptionHandler for multiple controllers. This way, we can define how to treat an exception in just one place because this handler will be called whenever the exception is thrown from classes that are covered by ControllerAdvice.
    • As the name suggests, is “Advice” for multiple controllers.
    • Allows our class to be a global interceptor of exceptions thrown by methods annotated by @RequestMapping.
  • @ExceptionHandler: Spring annotation that provides a mechanism to treat exceptions that are thrown during the execution of handlers (Controller operations). This annotation, if used on methods of controller classes, will serve as the entry point for handling exceptions thrown within this controller only. 
  • @ResponseStatus: Our error responses are always giving us the HTTP status 500 instead of a more descriptive status code. To address this we can annotate our Exception with @ResponseStatus and pass in the desired HTTP response status.

You can also override the existing exception handlers. Spring Boot’s built-in exception class ResponseEntityExceptionHandler has multiple methods that you can override to customize the exception handling further.

Altogether, the most common way is to use @ExceptionHandler on methods of @ControllerAdvice classes so that the exception handling will be applied globally or to a subset of controllers.

@ExceptionHandler and @ControllerAdvice are used to define a central point for treating exceptions and wrapping them up in a class.

Create a spring starter project with dependencies Lombok, MySQL Driver, Spring Data JPA, and Spring Web.

In application.properties

spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.jpa.hibernate.ddl-auto=update
@Entity
@Table(name = "emp")
@Data
@NoArgsConstructor
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
@AllArgsConstructor
public class ErrorType {
    private String message;
    private String code;
    private String error;
    private String classType;
}
@Getter
public class BusinessException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    private String message;
    private HttpStatus code;

    public BusinessException(String message, HttpStatus code) {
        super(message); // pass message to RuntimeException()
        this.message = message;
        this.code = code;
    }
}
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class EmptyInputException extends RuntimeException {

    private static final long serialVersionUID = 1L;
    private String message;
    private String code;
}

Create a class extending from the ResponseEntityExceptionHandler class and add @ControllerAdvice, and @ResponseStatus annotation on top of the class.

@ControllerAdvice
@ResponseStatus
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(EmptyInputException.class)
    public ResponseEntity<ErrorType> handleEmptyInput(
               EmptyInputException emptyInputException) {

        return new ResponseEntity<>(
           new ErrorType(
                emptyInputException.getMessage(), 
                emptyInputException.getCode(),
                "Data Is Empty", "Employee"
           ), 
           HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Map<String, Object>> handleBusinessException(
                 BusinessException businessException) {
        Map<String, Object> exception = new HashMap<>();
        exception.put("message", businessException.getMessage());
        exception.put("code", businessException.getCode());
        return new ResponseEntity<Map<String, Object>>(exception, 
                  businessException.getCode());
    }

    // We can also change the default exceptions
    // For this, class should extend the ResponseEntityExceptionHandler
    // Override methods based on requirement
    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
            HttpRequestMethodNotSupportedException ex,
            HttpHeaders headers, HttpStatusCode status, 
            WebRequest request) {

        Map<String, Object> exception = new HashMap<>();
        exception.put("message", "Change HTTP Method Type");
        exception.put("code", HttpStatus.METHOD_NOT_ALLOWED);
        return new ResponseEntity<>(exception, 
                   HttpStatus.METHOD_NOT_ALLOWED);
    }
}
@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    public Employee addEmployee(Employee employee) {
        if (employee.getName() == null || employee.getName().isBlank()) {
            throw new EmptyInputException("Name Is Empty", "601");
        }
        Employee savedEmployee = employeeRepository.save(employee);
        return savedEmployee;
    }

    public Employee updateEmployee(Employee employee, Integer id) {
        Employee existingEmployee = getEmpById(id);
        existingEmployee.setName(employee.getName());
        return addEmployee(existingEmployee);
    }

    public List<Employee> getAllEmployees() {
        List<Employee> empList = employeeRepository.findAll();
        if (empList.isEmpty()) {
            throw new BusinessException("List Is Empty", 
                            HttpStatus.NO_CONTENT);
        }
        return empList;
    }

    public Employee getEmpById(Integer id) {
        Optional<Employee> employee = employeeRepository.findById(id);
        return employee.orElseThrow(() ->
           new BusinessException("Employee Not Found", 
                           HttpStatus.NOT_FOUND));
    }

    public void deleteEmpById(Integer id) {
        getEmpById(id); // check whether Employee exist or not
        employeeRepository.deleteById(id);
    }
}
@Controller
@RequestMapping("/api/emp")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @PostMapping
    public ResponseEntity<Employee> addEmployee(@RequestBody 
                   Employee employee) {
        Employee employeeSaved = employeeService.addEmployee(employee);
        return new ResponseEntity<Employee>(employeeSaved, 
                  HttpStatus.CREATED);
    }

    @GetMapping
    public ResponseEntity<List<Employee>> getAllEmployee() {
        List<Employee> listOfAllEmps = employeeService.getAllEmployees();
        return ResponseEntity.ok(listOfAllEmps);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Employee> findEmployeeById(
             @PathVariable Integer id) {
        return ResponseEntity.ok(employeeService.getEmployeeById(id));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteEmployeeById(@PathVariable Integer id) {
        employeeService.deleteEmpById(id);
        return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Employee> updateEmployeeById(
            @RequestBody Employee employee, @PathVariable Integer id) {
        Employee savedEmployee = 
                  employeeService.updateEmployee(employee, id);
        return new ResponseEntity<Employee>(savedEmployee, 
                  HttpStatus.CREATED);
    }
}

API Path:- {{base_url}}/api/emp

{
    "name": ""
}

Response:-

{
    "code": 601,
    "message": "Name Is Empty"
}

API Path:- {{url}}/api/emp/1
Response:-

{
    "code": "NOT_FOUND",
    "message": "Employee Not Found"
}
  1. NoSuchBeanDefinitionException
  2. NoUniqueBeanDefinitionException
  3. BeanCurrentlyInCreationException
  4. BeanInstantiationException
  5. ApplicationContextException

1. NoSuchBeanDefinitionException

This exception occurs when we don’t have any spring beans that we have Autowired in another class. Spring tries to find the definition of the class and tries to inject the dependencies, in this case, the bean is not available in the container therefore it will throw NoSuchBeanDefinitionException.

//@Component
// After commenting the above line (@Component),
// Student class is no longer a bean
public class Student {
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class University {

    @Autowired
    Student student;
}
// starter class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        try {
            SpringApplication.run(Demo1Application.class, args);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

2. NoUniqueBeanDefinitionException

Assume we have an interface, MyInterface. There are 2 implementation classes for this interface:- MyInterfaceImpl1 & MyInterfaceImpl2. We have a class A that has Autowired the dependency of this interface. Since MyInterfaceImpl1 & MyInterfaceImpl2 have @Component/@Service, therefore, two beans will be created of type MyInterface. So, the framework gets confused about which one class to autowire here.

public interface MyInterface {
}
@Component
public class MyInterfaceImpl1 implements MyInterface {
}
@Component
public class MyInterfaceImpl2 implements MyInterface {
}
@Component
public class Test {
    @Autowired
    MyInterface myInterface;
}

To resolve this error either use @Qualifier(“BeanName”) or @Primary.

Using @Primary:-

@Component
@Primary
public class MyInterfaceImpl1 implements MyInterface {
}

Using Qualifier:-

@Component
public class Test {
    @Autowired
    @Qualifier("myInterfaceImpl1")
    MyInterface myInterface;
}

What if one class (MyInterfaceImpl2) has @Primary and the Test class contains @Qualifier(“myInterfaceImpl1”) then which will have more preferences?

When both @Primary and @Qualifier are used together, @Qualifier takes precedence over @Primary.

In this scenario, even though MyInterfaceImpl2 is marked with @Primary, Spring will inject the bean specified by the @Qualifier annotation in the Test class (myInterfaceImpl1). The @Qualifier annotation explicitly requests a specific bean by name, overriding the default behavior of @Primary.

Note:- By default, the bean name is the lowercase of the class name (with the first letter in lowercase). Here, “MyInterfaceImpl1” is a class whereas “myInterfaceImpl1” is a bean.

3. BeanCurrentlyInCreationException

This exception occurs when there is a circular dependency b/w two classes and both are created using constructor injection. In this case, the Student will be searching for the Teacher, and the Teacher will be searching for the Student.

@Component
public class Student {
    Teacher teacher;
    @Autowired // creating bean through constructor injection
    public Student(Teacher teacher) {
        this.teacher = teacher;
    }
}
@Component
public class Teacher {
    Student student;
    @Autowired // creating bean through constructor injection
    public Teacher(Student student) {
        this.student = student;
    }
}

There are certain principles to resolve this issue. One of them is using @Lazy in one of those bean creations.

@Component
public class Teacher {
    Student student;
    @Lazy
    @Autowired
    public Teacher(Student student) {
        this.student = student;
    }
}

4. BeanInstantiationException

@Component
public class Sample {
    // spring will use this constructor to
    // initialize "Sample" class
    public Sample() {
        throw new RuntimeException(":)");
    }
}

5. ApplicationContextException

This exception is related to Spring Boot and it occurs when we miss @SpringBootApplication in the starter class. The framework will try to create a servlet container but without @SpringBootApplication it won’t be able to do that.

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *