Fundamentals 13 min read

Defensive Programming: Concepts, Core Principles, and Practical Cases

This article introduces defensive programming, explains its fundamental concepts and core principles such as risk identification and input validation, and demonstrates practical applications through multiple Java code examples covering pagination, loop prevention, exception handling, boundary checks, and assertions to improve software robustness.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Defensive Programming: Concepts, Core Principles, and Practical Cases

Defensive programming is a software development paradigm that anticipates and mitigates potential errors and exceptional conditions, aiming to keep applications stable in complex and unpredictable environments.

Basic Concept : It emphasizes proactive error prevention by considering possible faults during design and implementation, thereby reducing bugs and failures.

Core Principles include:

Risk identification – distinguishing non‑systemic risks (e.g., null pointers) from systemic risks (e.g., infinite loops).

Defensive rules – assume inputs are erroneous, minimize error impact, use assertions, write clear code, and continuously test.

Case 1 – Input Validation & Cleaning : For a web form, validate data types, length, range, and sanitize characters before processing.

Example (Java pagination service):

public class PaginationService {
    private static final int MAX_PAGE_SIZE = 100;

    /**
     * Get pagination info with parameter validation
     * @param totalRecords total number of records
     * @param pageSize records per page
     * @param pageNumber current page number
     * @return PaginationInfo containing total pages, current page, etc.
     */
    public PaginationInfo getPaginationInfo(int totalRecords, int pageSize, int pageNumber) {
        // Validate pageSize
        if (pageSize <= 0 || pageSize > MAX_PAGE_SIZE) {
            throw new IllegalArgumentException("pageSize must be a positive integer not exceeding " + MAX_PAGE_SIZE);
        }
        // Validate pageNumber
        if (pageNumber <= 0) {
            pageNumber = 1; // default to first page
        }
        int totalPages = (totalRecords + pageSize - 1) / pageSize;
        if (pageNumber > totalPages) {
            pageNumber = totalPages;
        }
        int startIndex = (pageNumber - 1) * pageSize;
        return new PaginationInfo(totalPages, pageNumber, startIndex);
    }
    // PaginationInfo is a simple DTO
    ...
}

This method first validates pageSize and pageNumber , throws an exception for illegal values, computes total pages, adjusts the page number if necessary, and returns a structured result.

Case 2 – Preventing Infinite Loops : Ensure loop exit conditions are reachable and validate loop parameters.

public List
generateList(int startMinutes, int endMinutes, int interval, int duration) {
    if (interval <= 0) {
        throw new IllegalArgumentException("Invalid parameters: interval must be positive integers.");
    }
    List
result = new ArrayList<>();
    int nextStartTime = startMinutes;
    while (nextStartTime <= endMinutes) { // avoid equality‑only condition
        int currentStart = nextStartTime;
        int currentEnd = currentStart + duration;
        result.add(currentStart + "-" + currentEnd);
        nextStartTime += interval;
    }
    return result;
}

Case 3 – Exception Handling : Wrap risky operations in try‑catch blocks, differentiate exception types, and log detailed error information.

public static String readFile(String filePath) {
    try {
        byte[] encoded = Files.readAllBytes(Paths.get(filePath));
        return new String(encoded);
    } catch (FileNotFoundException e) {
        log.info("File not found: " + filePath);
        return null;
    } catch (Exception e) {
        log.info("Error reading file: " + e.getMessage());
        return null;
    }
}

Case 4 – Boundary Condition Checks : Verify array indices and collection accesses before use.

public class ArrayAccess {
    public static void main(String[] args) {
        int[] numbers = {1,2,3,4,5};
        int index = getIndexFromUser(); // assume valid input
        if (index >= 0 && index < numbers.length) {
            log.info(numbers[index]);
        } else {
            log.info("Index out of bounds");
        }
    }
    private static int getIndexFromUser() {
        return 2; // example value
    }
}

Case 5 – Using Assertions : Apply assertions to verify internal invariants during development.

public static int calculateAge(int birthYear) {
    if (birthYear <= 0 || birthYear > java.time.Year.now().getValue()) {
        throw new IllegalArgumentException("Birth year must be a positive integer less than the current year");
    }
    int currentYear = java.time.Year.now().getValue();
    return currentYear - birthYear;
}

public static void main(String[] args) {
    try {
        int age = calculateAge(1990);
        log.info("Age is: " + age);
    } catch (IllegalArgumentException e) {
        log.info("Error: " + e.getMessage());
    }
}

Challenges : Over‑defending can bloat code and degrade performance; therefore, defensive measures should be applied judiciously, focusing on entry points (e.g., input validation) and critical loops.

Conclusion : Defensive programming is a proactive strategy that improves software quality and stability by foreseeing and preventing errors, making systems more resilient to unexpected inputs and runtime anomalies.

JavaBest Practicescode qualityerror handlingdefensive programmingsoftware robustness
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.