Best Practices for Writing Secure Java Code

Every Java developer should follow coding standards and best practices to develop secure Java code. It is critical your code is not vulnerable to exploits or malicious attacks. In recent times, even big organizations like eBay, the CIA, and the IRS have fallen victim to vulnerabilities in their applications that have been discovered and exploited by attackers. 

The following guidelines provide a solid foundation for writing secure Java code and applications. These will minimize the possibility of creating security vulnerabilities caused by Java developers and help prevent known malicious attacks. 

1. Only Use Tried and Tested Libraries 

A large percentage of the code in applications is sourced from public libraries and frameworks. These libraries can contain vulnerabilities that may allow a malicious attacker to exploit your application. 

Organizations trust their business and reputation to the libraries they use, so make sure you only use proven ones and keep them up to date with the latest versions. Consider checking if they have any known vulnerabilities or require any security fixes.

2. Avoid Serialization

Java serialization is inherently insecure which is why Oracle recently announced it has a long-term plan to remove it. Serialization vulnerabilities were recently found in Cisco and Jenkins applications. 

Any application that accepts serialized Java objects is vulnerable, even if a library or framework is responsible and not your own Java code. One area to watch out for is making an interface serializable without thinking through what could be exposed. Another pitfall to avoid is accidentally making a security-sensitive class serializable, either by subclassing or implementing a serializable interface.

3. Always Hash User Passwords

Never store any passwords as plain text. Always hash user passwords preferably using a salted hash and a recommended hashing algorithm like SHA-2. When a password has been ‘hashed’, it has been turned into a scrambled version of itself. Using a predefined key known to the application, the hash value is derived from a combination of both the password and the key using a hashing algorithm.

4. Filter Sensitive Information From Exceptions

Exception objects can contain sensitive information that can assist an attacker hoping to exploit your system. An attacker can manufacture input arguments to expose internal structures and mechanisms of the application. It’s important to remember that information can be leaked from the exception message text and the type of an exception.

Take for example the FileNotFoundException message. These messages contain information about the layout of the file system and the exception type reveals the missing requested file. 

To secure Java code applications, you should filter both exception messages and exception type.

5. Do Not Log Sensitive Information

Data thefts cause massive harm to individuals and organizations, and developers need to do everything possible to prevent them from happening. Information like credit and debit card numbers, bank account numbers, passport numbers, and passwords are highly sensitive and valuable to criminals. Don’t store this type of information in log files and make sure it’s not detectable through searches in cleartext. 

If you have to log any sensitive information like card numbers, for example, think about logging only part of the card number e.g. the last four digits, and make sure it’s encrypted using a proven library. Don’t write your own encryption functionality. 

6. Error Handling and Logging 

You can accidentally reveal sensitive information in user error messages and error messages recorded in the log files, such as account information or system details. 

A safer way is to use generic screen error messages for users. Additionally, write log error messages that will help support teams investigating production issues without providing an attacker with useful information to further exploit your systems.

7. Write Simple Java Code

Generally speaking, simple java code is secure java code. Here are some tips on keeping your code simple and secure:

  • Keep it as simple as possible without reducing functionality. 
  • Use code quality checking products like SonarQube. This tool will continuously inspect the code quality whilst checking for any new vulnerabilities in your latest code release. Once a bug or vulnerability is in production, it is a lot harder to fix it compared to the effort to prevent it in the first place. 
  • Expose the minimum amount of information in your code. Hiding implementation details is good for keeping your code both secure and maintainable. 
  • Make good use of Java’s access modifiers. Declare the most restrictive access levels for classes, methods, and their attributes possible. Set everything that can be set to private, as private. 
  • Always define the smallest possible API and interface objects. Decouple components and make them interact in the smallest scope possible. If one component of your application is compromised by a breach, the others will be safe.

8. Prevent Injection Attacks

An injection attack occurs when malicious code is injected into the network. This type of attack is considered a major problem in web application security and is listed as the number one security risk in the OWASP Top 10. Any application that allows users to enter or upload data might contain a vulnerability that can allow an injection attack. Insufficient user input validation is usually the primary reason injection vulnerabilities exist. 

SQL Injection 

SQL Injection vulnerabilities are created when developers write dynamic database queries that can include user input. An attacker can include SQL commands in the input data, in any screen input field. Then because of a vulnerability in the code, the application runs the rogue SQL in the database. This gives attackers a way to bypass the application’s authentication functionality and allow them to retrieve the contents of an entire database. 

Key things to remember to prevent SQL injections: 

  • Never build SQL statements by concatenating arguments. This allows a high probability of SQL injection attacks.
  • Avoid dynamic SQL. Use Prepared Statements (with parameterized queries). 
  • Use stored procedures. 
  • Whitelist input validation. 
  • Escape user-supplied input. 

XPath Injection 

XPath injections are similar to SQL injections in that they can attack websites that operate on user-supplied information to construct an XPath query for XML data. An attacker can gain detailed information on how the XML data is structured or access data that is not normally accessible by sending malicious information to the website. 

These vulnerabilities can also elevate the attacker’s privileges in the application if the XML data is being used for authentication. 

You can avoid XPath injection by similar techniques used to prevent SQL injection: 

  • Sanitize all user input. 
  • When sanitizing, verify the data type, format, length, and content. 
  • In client-server applications, perform validation at both the client and the server sides.
  • Thoroughly test applications especially user input. 

Cross-Site Scripting 

Cross-Site Scripting (XSS) attacks happen when an attacker uses a web application to send malicious code (usually a browser-side script) to other users. Vulnerabilities that allow these attacks can occur anywhere a web application receives input from a user, within the output it generates, without validating or encoding it. 

To keep Java code applications secure and prevent XSS, filter your inputs with a whitelist of allowed characters and use a proven library to HTML encode your output for HTML contexts. For JavaScript use JavaScript Unicode escapes. 

Summary 

In summation, there are some key points to bear in mind in writing secure Java code. You should always think about security in the development of your application in the design stage and code reviews, as well as look for vulnerabilities in your Java code and take advantage of the Java security APIs and libraries.

Only ever use highly rated vendor tools to monitor and log your code for security issues. This means you should investigate the full list of application attack types and follow the recommended prevention methods.

If you use these guidelines for writing secure Java code applications in your organization, you can protect yourself and your applications against malicious attacks and data theft.

Java Logging Guide: How To Do It Right

Log monitoring is something you want to plan and standardize before you start writing your code, especially if it involves different teams or separate locations.

During the last couple of years, we witnessed the strong connection between quality and standardized logging and the ability to track and resolve production problems.

In this post, we will focus on a few lessons we’ve learned about Java logging and how to do it right.

What is Java logging? 

Java logging, commonly known as logging, serves a crucial role in undetstanding system performance and identifying the root causes of failures. It aids the analysis of program executions by keeping a record of events, which makes it invaluable for tasks such as auditing and debugging.

However, it’s important to note that logging does not occur automatically. Developers must proactively implement logging rules to ensure they can effectively manage the logging process.

9 tips for effective Java logging Setting up your logging correctly is crucial for the future and can help you get the most from your logging. Here are some tips and Java logging best practices to get you started:

9 tips for effective Java logging

Setting up your logging correctly is crucial for the future and can help you get the most from your logging. Here are some tips and Java logging best practices to get you started:

1) Set your log severity right

Many times, too often actually,  we see a complete log file written with the same log severity. This makes your logs harder to understand and hides the important logs you want to notice.

To make it easier for you to decide what severity to set for each log, here are some simple log severity guidelines:

  • Debug/Verbose: Logs that are mainly used by the developers and contain data such as response times, health checks, queues status etc. An example for a debug log would be “Number of messages in the user creation queue = 3482”
  • Info: Business processes and transactions, these logs should be readable for QA, Support and even advanced users to understand the system’s behavior. An example of an info log will contain data on a product purchase on your e-commerce platform, a user creation on your social media or a successful batch process on your data analytics solution.
  • Warning: These logs mean something unusual happened or something isn’t right, but it does not necessarily mean that anything failed or the user will notice a problem. An example of a warning would be “Received illegal character for username – “Jame$” , ignoring char”  
  • Error: A problem that must be investigated; use the Error severity to log Disconnections, failed tasks or failures that reflect to your users. If you see an error in your log that does not require immediate investigation, you should probably lower its severity.
  • Critical/Fatal: Something terrible happened, stop everything and handle it,. This could beCrashes, Serious latency or performance issues, security problems. All these must be logged with the log severity Critical.

2) Remember you will not be the only one reading these logs

When writing your application logs, remember that besides you, other people will read these logs. Whether it’s programmers, QA or support consuming the logs you wrote, they better be clear and informative. 

On the other hand, logs that are long and detailed can be hard to parse automatically (grep, awk, etc.), so either you find a way to write a clear log that can also be parsed easily, or you can simply print two logs, one for humans and one for computers:

E.g – Print these two logs together, the first log for humans and the second for computers:

  • “transaction was completed successfully” + transactionID “total time for transaction =” + TimeElapsed
  • “success” + transactionID  “time” + TimeElapsed

3) Track your communication with other systems

Integration issues can be the hardest to debug; our suggestion is that you log every event that comes in/out of your system to an external system, whether it is HTTP headers, authentications, keep alive, etc.

In complex and high scale systems this can be a performance overhead, but in case you experience performance issues, you can always switch off the logging for that particular log level (usually Debug or Trace) and use it when something goes wrong with your production.

4) Add metadata to your logs

Often, programmers write great log text and severity but forget to add the log metadata such as Category, Class, method or threadID. 

Adding metadata to your logs can significantly enhance your capability of pinpointing production problems as you can search and identify problematic categories, classes or methods or follow a thread to understand the root cause of an error you see. The more metadata you add, the better your log is.

5) Use a logging API

Logging APIs make it much easier to add log destinations and integrate with logging tools seamlessly and without any code modifications. In addition, they make your logs more clear by standardizing them and enriching them with metadata fields such as thread ID.

The two most common logging API’s for Java are Log4J and Logback (slf4j).

Note that one of the greatest benefits of Log4J and Logback is that they allow you to send logs from any Java-based Apache project easily! (Kafka, Hazelcast, etc.)

A Logback log will be written in the following structure:

log.warning(“Retried {} times before succeeding to create user: ‘{}’”, retries, username);

The same log in Log4J would be:

log.warning(“retried” + retries + “times before succeeding to create user” + username);

6) Make sure you know what you are logging

When writing logs, especially when calling functions and variables within that log, make sure you understand what will be the outcome of that print. Bad logs can be:

  • inconsistent – values that arrive NULL or with different data types
  • Too long – Printing a list of URLs that is impossible to read or printing HEX values, for instance
  • Null – printing logs that rely on a variable that may or may not have content, for instance: log.error(monitor.get_ERR_reason)

7) Don’t write huge logs

It’s great to write detailed and descriptive logs, but many times we see single log entries with an enormous amount of characters (20K+) as the logs are used to store data that is completely unrelated to logs and should be managed separately. This can cause serious performance issues when writing logs to your disk and create bandwidth issues when using hosted logging solutions.  

Remember what the is the main purpose of your logs and stick to it. You want clear logs that tell the story of your software for you to understand its behavior and be able to debug it.

8) Log exceptions correctly

We all probably agree that reporting exceptions is a crucial part of the logging process. On that basis, some tend to both report the exception and then wrap it with their own custom exception and throw it again. This will probably cause the stack trace to be printed twice, a fact that will most likely cause confusion. We suggest never to report and re-throw, decide what works for you best and stick to it. 

We generally recommend throwing the exception with your own custom exception and catching them all in a centralized handler which will log them and handle any other activities that are needed.

Here are some examples of Java exception logging:

BAD:

try {

   Integer x = null;

   ++x;

} catch (Exception e) {

log.error(“IO exception”, e);

throw new MyCustomException(e);

}

BETTER:

try {

   Integer x = null;

   ++x;

} catch (Exception e) {

log.error(“IO exception”, e);

}

BEST:

try {

   Integer x = null;

   ++x;

} catch (Exception e) {

throw new MyCustomException(e);

}

As a rule of thumb, let the logging framework you are using help you log exceptions and don’t do it yourself. Remember, the first argument is always the text message; write something about the nature of the problem. 

Don’t include the exception message, as it will be printed automatically after the log statement preceding the stack trace. But to do so, you must pass the exception itself as the second argument; other logging standards will most likely cause the message to be printed wrongly.

BAD:

log.error(e);

log.error(e, e);  

log.error(“” + e);

log.error(e.toString());  

log.error(e.getMessage());

log.error(null, e);

log.error(“”, e);

log.error(“{}”, e);

log.error(“{}”, e.getMessage());

log.error(“Error reading configuration file: ” + e);

log.error(“Error reading configuration file: ” + e.getMessage());

GOOD:

log.error(“Error reading configuration file”, e);

9) Use an ID to track your events

This method will allow you to easily filter or search for a specific event that you want to track. The idea is that whoever is responsible for creating an event (e.g. client, worker etc) generates a unique ID that is passed through all functions and service calls that are used to process that event. Then once an exception or error occurs, it is simple to take the event ID from that error and query for its history throughout the different functions, services and components.

Closing thoughts

In the Java logging community, there are many logging methods that exist, which presents developers with a plethora of options to choose based on their specific needs and requirements. 

The selection of an appropriate logging approach can massively impact the effectiveness and efficiency of the logging process. The choice ultimately depends on the complexity of the project, the desired level of customisation, and the need for compatiability with existing systems. 

By following our tips above, you can create a well-executed logging strategy that can factilitate system monitoring and debugging, whilst enhancing the maintainability and staibility of Java applications.

(This blog post was updated August 2023)