Errors, Exceptions, Packages, Assertions, and Logging
Errors and exceptions are an inevitable part of programming. While we aim for correctness, errors can occur due to various reasons, such as invalid user input, resource limitations, or coding mistakes.
One way to signal an error is to return an invalid value, such as
-1 to indicate the end of a file or
null to represent the absence of a valid result.
However, this approach has limitations, as it may not always be
possible when there is no clearly defined invalid value.
This limitation brings to the topic of Exception handling.
To handle these situations effectively, Java employs the concept of exceptions, which allow programmers to signal and manage abnormal conditions in the code. Exception handling provides a structured approach to gracefully manage these errors, ensuring that the program does not crash unexpectedly.
Java provides a structured mechanism to handle errors gracefully using exceptions. This ensures that errors do not lead to program crash but allow recovery or meaningful failure notifications.
Example: Throwing and Catching Exceptions
public class ExceptionExample {
// Method to demonstrate throwing an exception
public static int divide(int a, int b) throws ArithmeticException {
// Check for division by zero
if (b == 0) {
// Throw an ArithmeticException with a meaningful message
throw new ArithmeticException("Division by zero is not allowed.");
}
// Perform division if no exception occurs
return a / b;
}
public static void main(String[] args) {
try {
// Attempt to divide numbers, expecting a successful result
System.out.println("Result: " + divide(10, 2));
// Attempt to divide by zero, which will throw an exception
System.out.println("Result: " + divide(10, 0));
} catch (ArithmeticException e) {
// Catch the exception and handle it gracefully
System.out.println("Error: " + e.getMessage());
} finally {
// Optional: The `finally` block executes regardless of exceptions
System.out.println("Division operation completed.");
}
}
}
Output:
Result: 5
Error: Division by zero is not allowed.
Division operation completed.
divide method checks if the denominator
b is zero. If b == 0, it throws an
ArithmeticException with a custom message. This
demonstrates how Java detects and raises errors.
try block in main attempts to execute code that
might throw an exception. If an exception is
thrown, the catch block catches it and prints a
meaningful error message instead of crashing.
finally block
is optional and always executes, even if no exception is
thrown. It is useful for cleanup operations.
Throwable
|
|-- Error (Unrecoverable JVM-level issues)
|
|-- Exception
|
|-- RuntimeException (Unchecked exceptions)
|
|-- Checked Exceptions
Java organizes errors into a hierarchy under the parent class
Throwable. The main categories are:
Errors represent severe issues that are usually beyond the control of the programmer. They indicate problems that typically arise in the Java Virtual Machine (JVM) itself and are unlikely to be recoverable during runtime.
Key Characteristics:
Error class.Examples of Errors:
Code Example:
public class ErrorExample {
public static void main(String[] args) {
// Example of a StackOverflowError
causeStackOverflow();
}
public static void causeStackOverflow() {
// Recursive call without termination
causeStackOverflow();
}
}
Exceptions represent conditions that a program should handle gracefully. They are recoverable, and handling them appropriately allows the program to continue executing.
Key Characteristics:
Exception class.Unchecked exceptions occur due to programming mistakes that could have been avoided with proper input validation or checks. The compiler does not enforce handling or declaring these exceptions.
Key Characteristics:
RuntimeException class.
throws keyword.
Examples of Unchecked Exceptions:
null.
Code Example:
public class UncheckedExceptionExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3};
try {
// Attempt to access an invalid index
System.out.println(numbers[5]);
} catch (ArrayIndexOutOfBoundsException e) {
// Handle the unchecked exception
System.out.println("Error: " + e.getMessage());
}
}
}
Output:
Error: Index 5 out of bounds for length 3
Checked exceptions represent exceptional conditions that the
program should anticipate and handle. The Java compiler enforces
that these exceptions must be declared using the
throws keyword or handled using a
try-catch block.
Key Characteristics:
Exception class
(excluding RuntimeException).
Examples of Checked Exceptions:
Code Example:
import java.io.*;
public class CheckedExceptionExample {
public static void main(String[] args) {
try {
readFile("example.txt");
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
}
}
// Method that throws a checked exception
public static void readFile(String fileName) throws IOException {
FileReader file = new FileReader(fileName);
BufferedReader br = new BufferedReader(file);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
Output:
An error occurred: example.txt (No such file or directory)
FileReader and BufferedReader are
used to read a file.
FileReader may throw an
IOException if the file doesn't exist or can't be
accessed.
readFile method declares
throws IOException, meaning the caller must
handle it.
main(), we use a try-catch block
to handle the IOException.
Since IOException is a Checked Exception, the
compiler forces us to handle or declare it.
Java allows programmers to create their own checked exceptions.
This is useful when custom rules or constraints need to be
enforced.
Note: This is also an example of
Custom Exception.
Code Example:
// Custom exception class extending Exception (checked exception)
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message); // Pass the message to the parent Exception class
}
}
public class UserDefinedCheckedException {
// Method that throws a user-defined checked exception
public void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
// Throw custom checked exception if age is less than 18
throw new InvalidAgeException("Age must be 18 or older.");
}
System.out.println("Age validation passed. You are eligible.");
}
public static void main(String[] args) {
UserDefinedCheckedException example = new UserDefinedCheckedException();
try {
// Test with invalid age
example.validateAge(16);
} catch (InvalidAgeException e) {
// Handle the exception
System.out.println("Caught Exception: " + e.getMessage());
}
try {
// Test with valid age
example.validateAge(20);
} catch (InvalidAgeException e) {
System.out.println("Caught Exception: " + e.getMessage());
}
}
}
Output:
Caught Exception: Age must be 18 or older.
Age validation passed. You are eligible.
InvalidAgeException):
This class extends Exception, making it a
checked exception. It has a constructor that
takes a message and passes it to the parent class.
UserDefinedCheckedException Class:
Contains the validateAge(int age) method, which
checks if age is less than 18. If met, it
throws an
InvalidAgeException with a custom message.
main): Creates an
instance, calls validateAge(16) which throws an
exception (caught and handled), and then calls
validateAge(20) which passes and prints a success
message.
| Aspect | Checked Exceptions | Unchecked Exceptions |
|---|---|---|
| Declaration | Must be declared using throws. |
No need to declare. |
| Compiler Enforcement | Enforced by the compiler. | Not enforced by the compiler. |
| Use Case | Anticipated conditions; recoverable. | Programming errors; logical flaws. |
| Examples |
IOException, SQLException.
|
ArithmeticException,
NullPointerException.
|
| Inheritance |
Extends Exception class (not
RuntimeException).
|
Extends RuntimeException class. |
Exception handling in Java revolves around three main constructs:
try Block: Encapsulates code that
may generate an exception.
catch Block: Handles the exception
if it occurs.
finally Block: Executes cleanup
code regardless of whether an exception was thrown.
try {
// Code that might throw an exception
}
catch (ExceptionType1 e) {
// Handle specific exception
}
catch (ExceptionType2 e) {
// Handle another type of exception
}
finally {
// Cleanup code
}
Handle a specific type of exception.
// Demonstrating a single checked exception using IOException
import java.io.IOException;
public class SingleExceptionExample {
public static void main(String[] args) {
// Use a try-catch block to handle the IOException
try {
// Simulate an I/O operation that throws an exception
throw new IOException("Simulated I/O error");
} catch (IOException e) {
// Handle the exception and display the error message
System.out.println("Caught an IOException: " + e.getMessage());
}
// Program continues execution after handling the exception
System.out.println("Program execution continues smoothly.");
}
}
Output:
Caught an IOException: Simulated I/O error
Program execution continues smoothly.
try block
contains code that may throw an exception. In this case, the
throw statement deliberately throws an
IOException with a custom error message.
throw statement creates and throws an
IOException object, simulating an error scenario.
IOException. The getMessage() method
retrieves the error message.
Use multiple catch blocks for different exception
types. The blocks are evaluated in sequence.
// Demonstrating multiple catch blocks to handle specific and general exceptions
import java.io.FileNotFoundException;
import java.io.IOException;
public class MultipleExceptionsExample {
public static void main(String[] args) {
// Use a try-catch block to handle multiple exception types
try {
// Simulate a file-related error by throwing FileNotFoundException
throw new FileNotFoundException("File not found error");
} catch (FileNotFoundException e) {
// Catch and handle the more specific exception (FileNotFoundException)
System.out.println("Caught FileNotFoundException: " + e.getMessage());
} catch (IOException e) {
// Catch and handle the more general exception (IOException)
System.out.println("Caught IOException: " + e.getMessage());
}
// Program continues execution after handling the exceptions
System.out.println("Program execution continues smoothly.");
}
}
Note: Arrange catch blocks from the most specific
to the most general. For example, IOException should
follow FileNotFoundException.
Output:
Caught FileNotFoundException: File not found error
Program execution continues smoothly.
FileNotFoundException.
FileNotFoundException (specific). The
second block catches IOException (general).
Java allows developers to explicitly throw exceptions using the
throw keyword. When a method can throw exceptions, it
must declare them using the throws keyword in the
method signature.
Compiler Enforcement: The Java compiler ensures that checked exceptions are either caught or declared in the calling code.
Code Example:
// Class to demonstrate exceptions with the `throws` keyword
public class ThrowsKeywordExample {
/**
* This method validates a person's age for voting eligibility.
* It uses the `throws` keyword to declare that it can throw an
* IllegalArgumentException.
*
* @param age The age of the person.
* @throws IllegalArgumentException if the age is less than 18.
*/
public void validateAge(int age) throws IllegalArgumentException {
// Check if the age is less than 18
if (age < 18) {
// Throw an IllegalArgumentException with a custom message
throw new IllegalArgumentException("Age must be 18 or above to vote.");
}
// If no exception occurs, print a success message
System.out.println("Age is valid for voting.");
}
public static void main(String[] args) {
// Create an instance of the class
ThrowsKeywordExample example = new ThrowsKeywordExample();
// Test cases with different ages
int[] testAges = {16, 20, 15, 25};
for (int age : testAges) {
try {
// Validate the age using the method
example.validateAge(age);
} catch (IllegalArgumentException e) {
// Handle the exception and print the error message
System.out.println("Caught an exception: " + e.getMessage());
}
}
}
}
Output:
Caught an exception: Age must be 18 or above to vote.
Age is valid for voting.
Caught an exception: Age must be 18 or above to vote.
Age is valid for voting.
throws:
The validateAge method declares that it may throw
an IllegalArgumentException using the
throws keyword.
throw statement is used inside the method to
explicitly throw the exception when the condition (age <
18) is met.
main method uses a try-catch block
to call the method and handle any exceptions that are thrown.
Custom exceptions can be created by extending the
Exception class. This allows developers to define
application-specific errors.
// Custom exception class to handle negative values
class NegativeValueException extends Exception {
// Constructor to initialize the exception with a custom message
public NegativeValueException(String message) {
super(message); // Pass the message to the parent Exception class
}
}
// A class representing a simple linear list
public class LinearList {
/**
* Adds a value to the list.
*
* @param value The value to be added.
* @throws NegativeValueException If the value is negative.
*/
public void add(int value) throws NegativeValueException {
// Check if the value is negative
if (value < 0) {
// Throw a custom exception for negative values
throw new NegativeValueException("Negative value: " + value);
}
// Print a success message if the value is valid
System.out.println("Value added: " + value);
}
public static void main(String[] args) {
// Create an instance of the LinearList class
LinearList list = new LinearList();
// Use a try-catch block to handle the custom exception
try {
// Attempt to add a negative value
list.add(-10);
} catch (NegativeValueException e) {
// Handle the custom exception and display the error message
System.out.println("Caught Exception: " + e.getMessage());
}
// Attempt to add a valid positive value
try {
list.add(15);
} catch (NegativeValueException e) {
System.out.println("Caught Exception: " + e.getMessage());
}
// Program continues execution smoothly after handling exceptions
System.out.println("Program execution continues.");
}
}
Output:
Caught Exception: Negative value: -10
Value added: 15
Program execution continues.
NegativeValueException class extends
Exception, making it a checked exception.
add method declares that it may throw a
NegativeValueException.
add method, if the input is negative, a
NegativeValueException is thrown.
main method catches it and handles it gracefully.
finally for Cleanup
The finally block ensures that critical cleanup code
runs regardless of whether an exception occurs.
// Demonstrating the use of finally for resource cleanup
public class FinallyExample {
public static void main(String[] args) {
// Simulate a resource by using a custom resource object
CustomResource resource = null;
try {
// Allocate the resource
resource = new CustomResource();
System.out.println("Resource initialized successfully.");
// Simulate an operation that throws an exception
int result = 10 / 0; // This will cause an ArithmeticException
System.out.println("Operation result: " + result);
} catch (ArithmeticException e) {
// Handle the specific exception
System.out.println("Error occurred: " + e.getMessage());
} finally {
// Ensure the resource is cleaned up, regardless of an exception
if (resource != null) {
resource.cleanup();
}
System.out.println("Cleanup completed in finally block.");
}
// Program continues after exception handling
System.out.println("Program execution continues.");
}
}
// A custom resource class to simulate resource management
class CustomResource {
// Constructor to initialize the resource
public CustomResource() {
System.out.println("CustomResource allocated.");
}
// Method to release the resource
public void cleanup() {
System.out.println("CustomResource cleaned up.");
}
}
Output:
CustomResource allocated.
Resource initialized successfully.
Error occurred: / by zero
CustomResource cleaned up.
Cleanup completed in finally block.
Program execution continues.
CustomResource class simulates a resource needing
manual cleanup.
try Block: Initializes the
resource and intentionally causes an arithmetic exception.
catch Block: Handles the
ArithmeticException.
finally Block: Ensures the
resource is cleaned up via the cleanup method,
running unconditionally.
finally: Ideal for releasing
resources such as file handles, database connections, or
network sockets.
Java supports chaining exceptions to provide more context about an
error. The Throwable class provides methods such as
getCause() and initCause() to work with
chained exceptions.
// Demonstrating exception chaining in Java
public class ExceptionChainingExample {
public static void main(String[] args) {
try {
// Outer try-catch block to catch and handle RuntimeException
try {
// Inner try-catch block to simulate and handle an underlying exception
throw new IllegalArgumentException("Invalid input provided");
} catch (IllegalArgumentException e) {
// Wrap the original exception (cause) into a higher-level exception
throw new RuntimeException("Processing failed due to invalid input", e);
}
} catch (RuntimeException e) {
// Handle the chained RuntimeException and display its message
System.out.println("Caught Exception: " + e.getMessage());
// Retrieve and display the cause of the RuntimeException
Throwable cause = e.getCause();
if (cause != null) {
System.out.println("Caused by: " + cause.getMessage());
}
}
// Program execution continues after handling the exception
System.out.println("Program execution continues.");
}
}
Output:
Caught Exception: Processing failed due to invalid input
Caused by: Invalid input provided
Program execution continues.
try-catch Block: Simulates
an error by throwing an IllegalArgumentException,
catches it, and wraps it inside a
RuntimeException.
try-catch Block: Catches
the RuntimeException and retrieves the root cause
using the getCause() method.
Methods Used:
getMessage(): Fetches the message of the
exception.
getCause(): Retrieves the cause of the current
exception.
In Java, a package serves as an organizational unit for grouping related classes and interfaces. Packages help prevent naming conflicts by allowing developers to create unique namespaces. They also facilitate better code organization and modularity.
To use classes from a package, the import statement
is employed:
import java.math.BigDecimal; // Imports the BigDecimal class
import java.math.*; // Imports all classes in the java.math package
* imports all classes in the specified package but
does not include sub-packages. For instance:
import java.math.* imports all classes in
java.math, but not java.math.geometry.
import java.* is not allowed.Developers can define custom packages by adhering to Java's naming conventions. The convention for package names follows the reverse of an organization's Internet domain name:
| Internet Domain | Package Name |
|---|---|
onlinedegree.iitm.ac.in |
in.ac.iitm.onlinedegree |
To include a class in a specific package, add a package
declaration at the top of the .java file:
// File: in/ac/iitm/onlinedegree/Employee.java
package in.ac.iitm.onlinedegree;
/**
* Represents an Employee with a name.
*/
public class Employee {
private String name; // Employee's name
// Constructor to initialize the employee's name
public Employee(String name) {
this.name = name;
}
// Getter method for the employee's name
public String getName() {
return name;
}
}
Create a file Main.java in the root directory or
another package.
// File: Main.java
// Import the Employee class from the specified package
import in.ac.iitm.onlinedegree.Employee;
/**
* Demonstrates the usage of packages in Java.
*/
public class Main {
public static void main(String[] args) {
// Create an Employee object
Employee emp = new Employee("John Doe");
// Access the public method of the Employee class
System.out.println("Employee Name: " + emp.getName());
}
}
Output:
Employee Name: John Doe
package statement at the top of
Employee.java specifies that the class belongs to
the in.ac.iitm.onlinedegree package.
import statement in Main.java makes
the Employee class accessible.
Directory Structure:
root/
├── in/
│ ├── ac/
│ │ ├── iitm/
│ │ │ ├── onlinedegree/
│ │ │ │ ├── Employee.java
├── Main.java
Visibility modifiers in Java allow developers to control access to classes, methods, and variables. This ensures encapsulation, one of the core principles of object-oriented programming (OOP). Proper use of visibility modifiers leads to better code security, modularity, and maintainability.
1. Create the VisibilityExample Class
// File: in/ac/iitm/onlinedegree/VisibilityExample.java
package in.ac.iitm.onlinedegree;
/**
* Demonstrates different visibility modifiers.
*/
public class VisibilityExample {
public int publicVar = 100; // Accessible everywhere
private int privateVar = 200; // Accessible only within this class
int packagePrivateVar = 300; // Accessible only within the same package
protected int protectedVar = 400; // Accessible within package and in subclasses
// Public method: accessible everywhere
public void showPublic() {
System.out.println("Public Variable: " + publicVar);
}
// Private method: accessible only within this class
private void showPrivate() {
System.out.println("Private Variable: " + privateVar);
}
// Package-private method: accessible only within the same package
void showPackagePrivate() {
System.out.println("Package-Private Variable: " + packagePrivateVar);
}
// Protected method: accessible within package and subclasses
protected void showProtected() {
System.out.println("Protected Variable: " + protectedVar);
}
public void demonstratePrivateAccess() {
// Private members are accessible within the same class
showPrivate();
}
}
2. Create a Class in the Same Package
// File: in/ac/iitm/onlinedegree/SamePackageAccess.java
package in.ac.iitm.onlinedegree;
/**
* Demonstrates access levels within the same package.
*/
public class SamePackageAccess {
public static void main(String[] args) {
VisibilityExample example = new VisibilityExample();
// Accessing public member
example.showPublic();
// Accessing package-private member
example.showPackagePrivate();
// Accessing protected member
example.showProtected();
// Accessing private member (will not compile if uncommented)
// example.showPrivate(); // ERROR: Private members are not accessible
}
}
3. Create a Class in a Different Package
// File: DifferentPackageAccess.java
package different;
import in.ac.iitm.onlinedegree.VisibilityExample;
/**
* Demonstrates access levels from a different package.
*/
public class DifferentPackageAccess extends VisibilityExample {
public static void main(String[] args) {
VisibilityExample example = new VisibilityExample();
// Accessing public member
example.showPublic();
// Accessing package-private member (will not compile if uncommented)
// example.showPackagePrivate(); // ERROR: Package-private members are not accessible outside the package
// Accessing protected member through inheritance
DifferentPackageAccess inherited = new DifferentPackageAccess();
inherited.showProtected();
// Accessing private member (will not compile if uncommented)
// example.showPrivate(); // ERROR: Private members are not accessible
}
}
Output:
For SamePackageAccess Class:
Public Variable: 100
Package-Private Variable: 300
Protected Variable: 400
For DifferentPackageAccess Class:
Public Variable: 100
Protected Variable: 400
SamePackageAccess and
DifferentPackageAccess.
VisibilityExample class. Demonstrated by calling
showPrivate() within a
public method.
SamePackageAccess because it belongs to the same
package.
SamePackageAccess (same package). Accessible in
DifferentPackageAccess only through inheritance.
In Java, when developing functions or methods, it is important to ensure that the input parameters meet certain expectations or constraints for the function to work correctly. If these constraints are violated, the behavior of the program can become unpredictable or erroneous.
To handle this, developers often rely on two primary mechanisms: exceptions and assertions.
For public functions that are accessed by external code, it is
common to enforce parameter constraints using exceptions. For
example: If a negative value is passed to the function, an
IllegalArgumentException is thrown. This approach
ensures that the function's contract is upheld and communicates
the error to the calling code.
public class MyFunctionExample {
// Method to calculate the square root of x, assuming x >= 0
public static double myfn(double x) throws IllegalArgumentException {
// Check if x is less than 0 and throw an exception if true
if (x < 0) {
throw new IllegalArgumentException("x < 0: Cannot calculate the square root of a negative number.");
}
// Return the square root of x if valid
return Math.sqrt(x);
}
public static void main(String[] args) {
// Test cases to demonstrate the behavior of myfn
// Test case 1: Valid input (positive number)
try {
double result1 = myfn(16); // Expected output: 4.0
System.out.println("Square root of 16: " + result1);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
// Test case 2: Valid input (0)
try {
double result2 = myfn(0); // Expected output: 0.0
System.out.println("Square root of 0: " + result2);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
// Test case 3: Invalid input (negative number)
try {
double result3 = myfn(-4); // Should throw an exception
System.out.println("Square root of -4: " + result3);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage()); // Expected error message
}
}
}
Output:
Square root of 16: 4.0
Square root of 0: 0.0
Error: x < 0: Cannot calculate the square root of a negative number.
Assertions provide a lightweight mechanism for validating assumptions during development. Unlike exceptions, assertions are typically used for internal, private methods where parameter constraints are assumed to be met by the developer's code.
public class AssertionExample {
// Method to calculate the square root of x, assuming x >= 0 (checked with an assertion)
public static double myfn(double x) {
// Use assertion to ensure that x is non-negative
assert x >= 0 : "x must be non-negative"; // Throws AssertionError if x < 0
return Math.sqrt(x);
}
public static void main(String[] args) {
// Enabling assertions requires the JVM to be run with the -ea flag.
// Example: java -ea AssertionExample
// Test case 1: Valid input (positive number)
try {
double result1 = myfn(16); // Expected output: 4.0
System.out.println("Square root of 16: " + result1);
} catch (AssertionError e) {
System.out.println("Assertion failed: " + e.getMessage());
}
// Test case 2: Valid input (0)
try {
double result2 = myfn(0); // Expected output: 0.0
System.out.println("Square root of 0: " + result2);
} catch (AssertionError e) {
System.out.println("Assertion failed: " + e.getMessage());
}
// Test case 3: Invalid input (negative number)
try {
double result3 = myfn(-4); // Should throw AssertionError
System.out.println("Square root of -4: " + result3);
} catch (AssertionError e) {
System.out.println("Assertion failed: " + e.getMessage()); // Expected error message
}
}
}
Running the Program:
To run the program and ensure assertions are enabled, you must
specify the -ea (enable assertions) flag when running
the program. Here’s how you would run the program from the command
line:
java -ea AssertionExample
Output:
Square root of 16: 4.0
Square root of 0: 0.0
Assertion failed: x must be non-negative
AssertionError is thrown, aborting the program.
Assertions in Java can be enabled or disabled at runtime using JVM options, providing flexibility without needing to modify or recompile the code. This allows developers to control when and where assertions should be active, aiding in debugging and development while avoiding unnecessary overhead in production environments.
Enable Assertions: Use -ea or
-enableassertions
java -ea MyCodejava -ea:com.example.MyClass MyCode
java -ea:com.example.package MyCode
Disable Assertions: Use -da or
-disableassertions
java -da MyCodejava -da:com.example.MyClass MyCode
java -da:com.example.package MyCode
Combining Options:
Java allows combining enabling and disabling assertions for
specific parts of the application. For example:
java -ea:com.example.package -da:com.example.package.MyClass MyCode
| Option | Effect |
|---|---|
-ea or -enableassertions |
Enable assertions globally or for specific classes or packages. |
-da or -disableassertions |
Disable assertions globally or for specific classes or packages. |
-ea:package.name |
Enable assertions for all classes in the specified package. |
-ea:package.name.ClassName |
Enable assertions for a specific class in a package. |
-da:package.name |
Disable assertions for all classes in the specified package. |
-da:package.name.ClassName |
Disable assertions for a specific class in a package. |
| Feature | Assertions | Exceptions |
|---|---|---|
| Purpose | Validate assumptions during testing | Handle runtime errors gracefully |
| When Used | Development and debugging | Production |
| Runtime Behavior | Disabled in production | Always enabled |
| Handling | Do not catch | Should be caught and handled |
Effective logging is essential for diagnosing issues and maintaining traceability in software systems. While print statements are a simple way to track program behavior, they lack flexibility, clutter the code, and are difficult to manage in complex systems. Logging provides a structured and configurable solution for generating diagnostic messages, enabling developers to monitor, debug, and audit their applications efficiently.
Print statements have significant drawbacks:
Logging addresses these issues by:
The simplest way to log messages in Java is to use the global logger:
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggingExample {
public static void main(String[] args) {
// Obtain the global logger
Logger globalLogger = Logger.getGlobal();
// Example 1: Default logging behavior
System.out.println("Example 1: Default Logging Behavior");
globalLogger.info("Default: Application started.");
globalLogger.warning("Default: A potential issue detected.");
globalLogger.severe("Default: Critical error occurred!");
// Example 2: Suppress logging by setting level to OFF
System.out.println("\nExample 2: Logging Suppressed");
globalLogger.setLevel(Level.OFF); // Suppress all logs
globalLogger.info("This log will not be shown.");
globalLogger.warning("This log will not be shown either.");
globalLogger.severe("Even severe logs are suppressed.");
// Example 3: Customizing the log level
System.out.println("\nExample 3: Custom Log Level");
globalLogger.setLevel(Level.WARNING); // Log only WARNING and above
globalLogger.info("This INFO log is suppressed.");
globalLogger.warning("This WARNING log is displayed.");
globalLogger.severe("This SEVERE log is displayed.");
}
}
Output:
Example 1: Default logging Behaviour
Example 1: Default Logging Behavior
Jan 8, 2025, 10:30:15 PM LoggingExample main
INFO: Default: Application started.
Jan 8, 2025, 10:30:15 PM LoggingExample main
WARNING: Default: A potential issue detected.
Jan 8, 2025, 10:30:15 PM LoggingExample main
SEVERE: Default: Critical error occurred!
Example 2: Logging Suppressed
Example 2: Logging Suppressed
(No logs are displayed.)
Example 3: Custom Log Level
Example 3: Custom Log Level
Jan 8, 2025, 10:30:15 PM LoggingExample main
WARNING: This WARNING log is displayed.
Jan 8, 2025, 10:30:15 PM LoggingExample main
SEVERE: This SEVERE log is displayed.
In Java, custom loggers allow you to organize and manage logging more effectively, especially in larger projects. Loggers can be structured hierarchically, similar to package names, providing fine-grained control over logging for specific parts of your application.
import java.util.logging.Level;
import java.util.logging.Logger;
public class CustomLoggerExample {
// Creating a custom logger
private static final Logger parentLogger = Logger.getLogger("in.ac.iitm");
private static final Logger childLogger = Logger.getLogger("in.ac.iitm.onlinedegree");
public static void main(String[] args) {
System.out.println("Custom Logger Example:");
// Example 1: Default logging behavior of custom loggers
System.out.println("\nExample 1: Default Logging Behavior");
parentLogger.info("Parent logger: This is an informational message.");
childLogger.info("Child logger: This is an informational message.");
// Example 2: Setting log levels for the parent logger
System.out.println("\nExample 2: Setting Parent Logger Level");
parentLogger.setLevel(Level.WARNING); // Logs only WARNING and SEVERE for parent and its children
parentLogger.info("Parent logger: This INFO message is suppressed.");
parentLogger.warning("Parent logger: This WARNING message is displayed.");
childLogger.info("Child logger: This INFO message is suppressed by the parent.");
childLogger.severe("Child logger: This SEVERE message is displayed.");
// Example 3: Setting log levels specifically for the child logger
System.out.println("\nExample 3: Setting Child Logger Level");
childLogger.setLevel(Level.INFO); // Logs INFO, WARNING, and SEVERE for the child logger
parentLogger.warning("Parent logger: This WARNING message is displayed.");
childLogger.info("Child logger: This INFO message is now displayed.");
childLogger.warning("Child logger: This WARNING message is displayed.");
}
}
Output:
Example 1: Default Logging Behavior
Example 1: Default Logging Behavior
Jan 8, 2025, 10:30:15 PM in.ac.iitm
INFO: Parent logger: This is an informational message.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
INFO: Child logger: This is an informational message.
Example 2: Setting Parent Logger Level
Example 2: Setting Parent Logger Level
Jan 8, 2025, 10:30:15 PM in.ac.iitm
WARNING: Parent logger: This WARNING message is displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
SEVERE: Child logger: This SEVERE message is displayed.
Example 3: Setting Child Logger Level
Example 3: Setting Child Logger Level
Jan 8, 2025, 10:30:15 PM in.ac.iitm
WARNING: Parent logger: This WARNING message is displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
INFO: Child logger: This INFO message is now displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
WARNING: Child logger: This WARNING message is displayed.
Java provides seven logging levels to categorize messages:
You can control the level of logging:
logger.setLevel(Level.FINE);
logger.setLevel(Level.ALL);
logger.setLevel(Level.OFF);
Logging behavior can also be controlled externally using a configuration file. This allows you to adjust logging levels or direct output without modifying the code.
Advantages of Logging: