Fix the 5 most common types of runtime errors in Java
The key to bug-free code is an awareness of the most common types of runtime errors in Java, along with the knowledge of how to avoid them.
As the name implies, runtime errors occur during the execution of a software program. They occur when an application runs. Any time an application behaves in a way that negatively deviates from its established requirements, this means a runtime error has occurred.
Such a broad definition isn't very helpful when Java runtime errors stop your app in its tracks. Let's take a closer look at the five most common runtime errors in Java programmers will encounter, and the steps and strategies to pursue to address them.
Top 5 runtime errors in Java
The most common runtime errors in Java can be broken into the following five categories:
- Data entry errors
- Insufficient runtime resources
- Poorly implemented logic
- External resource changes
- Third-party vulnerabilities
Input sanitization failures
There are innumerable ways user input can corrupt an application.
On an HTML-based comment board, a user who is allowed to innocently submit an unencoded less than (<) or greater than sign (>) has the potential to completely ruin the ability of that webpage to render. Similarly, text-processing systems built to expect all input in ASCII format can prematurely terminate if they receive emojis or nonstandard character inputs.
More nefariously, a favorite attack vector of hackers is SQL injection, which hides a harmful executable database query inside an otherwise innocuous input field. This not only can cause an application to fail, but potentially surrender control of the entire data layer.
The process of input sanitization, or data scrubbing, converts the broad spectrum of data that could potentially be entered into applications into a safe range of values that a program comprehends. Use of such libraries helps mitigate runtime errors caused by input sanitization failures.
Popular Java frameworks such as Apache BVal and Hibernate Validator perform simple, annotation-based input cleansing, and they integrate easily into any Java-based application.
Insufficient runtime resources
Software developers don't shoulder the blame for every type of runtime error that occurs. Many runtime errors in Java involve resource limitations caused by problems with the underlying infrastructure. Examples include: network timeouts, out of memory conditions, CPU overutilization or an inability to schedule a thread on the processor.
One way to avoid resource-related runtime errors is to use a load testing tool, such as JMeter or LoadRunner, in an application's CI/CD pipeline. If these tools detect a possible performance problem, they can stop the application before it moves further down the pipeline toward production deployment.
Some applications' load varies drastically. For example, a financial services app may see steady load most of the time but be extremely busy at the end of trading day. A tax service might hit a peak load before the filing deadline but have relatively little load the rest of the year.
DevOps teams must monitor their performance metrics with tools to preemptively detect and mitigate resource-related runtime errors. Examples of such tools include JDK Flight Recorder and Java Mission Control.
For applications with completely unpredictable workloads, use cloud-based load balancing technology to allocate resources elastically. This eliminates both underallocated resources, and the trap of purchasing expensive, rarely used hardware.
Poorly implemented logic
Just because code compiles doesn't mean it works properly. Code often contains logical problems that cause an application to fail at runtime.
Java contains a built-in construct to handle a class of common code-related runtime errors, called the RuntimeException, or the unchecked exception. Java 17 defines 78 such errors in the SDK alone, and other projects and frameworks additionally define their own RuntimeException errors.
The most commonly encountered RuntimeExceptions in Java include:
- ArithmeticException, for divide by zero errors;
- ClassCastException, for data type conversion errors;
- ConcurrentModificationException, for incorrectly implemented parallel computations;
- IndexOutOfBoundsException, when a nonexistent element in an array is accessed; and
- NullPointerException, when a method is invoked on a null object.
Developers are not required to handle unchecked exceptions in their code. But an unchecked exception that is thrown and ignored will terminate an application.
At the very least, every application should include a generic exception harness that can catch every possible RuntimeException, log the error and allow the problematic thread of execution to die rather than abort the entire application.
External resource configuration
Enterprise applications rarely exist in an isolated bubble. They typically interact with everything from NoSQL databases and relational systems to Kafka queues and RESTful APIs. Unfortunately, if your application is unable to connect to a required, external system, this inevitably results in a runtime error.
An external resource can precipitate a runtime error if any of the following situations occur with no corresponding update to the calling program:
- an IP address changes;
- credentials change;
- firewall configuration change; or
- the external system goes down for maintenance.
Applications should react nimbly when resources change. The 12-Factor App insists developers keep all configuration data external to the application so that applications can react nimbly when resources change. The ability to update property files without changing the codebase allows applications to deal with external resource changes, and avoids an application rebuild.
Relatedly, chaos engineering tools randomly terminate the processes upon which an application depends. Such tools force developers to write code that remains responsive and resilient even when external systems fail.
The inherent problem with external resources is that developers cannot control them -- but they can control how an application responds when those external resources fail. Anticipate runtime errors generated by the systems you don't control, and write applications that respond gracefully when those external systems fail.
Third-party library vulnerabilities
Any nontrivial enterprise application includes dozens of dependencies on third-party libraries to perform functions such as logging, monitoring, input validation, form handling and more.
Unfortunately, any bugs in a third-party library become a bug in the application you deploy. This became uncomfortably real for the Java world in December 2021, as an LDAP inject flaw in the widely used Log4j 2 library forced JVMs throughout the world to go offline.
One way to mitigate against the possibility that software dependencies introduce runtime errors into applications is to only use trusted libraries from organizations such as Apache or Eclipse.
Another protective measure is to regularly update an application's dependencies to the latest version as soon as updates become available.
Finally, be aware of secondary or tertiary dependencies -- ones that your primary dependencies themselves rely upon -- and be aware of any risks those bring with them.
In the world of software development, "perfect" is the enemy of "done." No program is immune from the threat of an unanticipated runtime error. However, when enterprise developers raise their awareness of the possible causes and take steps to mitigate against potential threats, they can create software that minimizes the probability of encountering a runtime error.