Back to Keywords: final
Q3Question 3 of 4

What happens if you declare final int x without initializing?

Key Concepts
  • For a final local: you must assign it before reading it (definite assignment rule), and you can assign it exactly once
  • For a final field (blank final): you must assign it in every constructor, and only once
  • A final local without assignment compiles – the error comes when you try to READ it before assigning
  • A blank final enables assigning different values in different constructors while keeping the field immutable after construction
  • Understanding this concept is essential for Java interviews at senior level

Definition

It compiles as a declaration, but you must assign it exactly once before reading it. For a local, the definite assignment rule applies. For a field, every constructor must assign it.

Two contexts:

  • final local variable: final int x; compiles. But you must assign it before any read, and you can assign it only once. This is a blank final local.
  • final instance field: private final int x; compiles. But every constructor must assign it exactly once. This is a blank final field.

A blank final is like a sealed form with one empty box. You must fill the box exactly once before the form is filed. For locals, you fill it before you use it. For fields, you fill it in the constructor before the object is complete.

Understanding the Concepts

Blank final local variable

JAVA
public void process() {
    final int fee;           // declaration without assignment – OK
    // System.out.println(fee);  // COMPILE ERROR: not yet assigned

    fee = calculateFee();    // first (and only) assignment – OK
    System.out.println(fee); // OK: now it has a value

    // fee = 0;              // COMPILE ERROR: already assigned
}

This is useful when you need to assign different values in different branches:

JAVA
final int fee;
if (isPremium) {
    fee = 0;
} else {
    fee = 50;
}
// fee is definitely assigned on both paths, and cannot be reassigned
System.out.println("Fee: " + fee);

Blank final instance field

JAVA
public class Account {
    private final String accountId;  // blank final field

    public Account(String accountId) {
        this.accountId = accountId;  // assigned in constructor
    }

    // If you add another constructor, it MUST also assign accountId:
    public Account() {
        this.accountId = "DEFAULT";  // must assign here too
    }
}

If any constructor forgets to assign a blank final field, the code does not compile.

What about final static fields?

JAVA
public class Config {
    private static final String BANK_NAME;  // blank final static

    static {
        BANK_NAME = "National Bank of India";  // assigned in static block
    }
}

A blank final static field must be assigned in a static initializer block.

What should a senior engineer know?

  • Blank finals enable immutable objects with flexible construction. Different constructors can set different values, but once set, the field never changes.
  • The JMM (Java Memory Model) guarantees that final fields are safely published after construction. This is the basis for thread-safe immutable objects.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  final int x;      ← blank final: declared but not assigned
  // read x here    ← COMPILE ERROR: not assigned yet
  x = 42;           ← first assignment: OK
  // read x here    ← OK: has value 42
  // x = 0;         ← COMPILE ERROR: already assigned, final

  Blank final field:
    private final String accountId;
    → must be assigned in EVERY constructor, exactly once
    → enables different values per constructor while staying immutable

Key Takeaway

Declaring final int x without initializing it is legal. This is called a blank final. You must assign it exactly once before reading it. For a local variable, the definite assignment rule enforces this. For an instance field, every constructor must assign it.

Blank finals are useful when you need to assign different values in different constructors or different branches of an if-else, while ensuring the value cannot be changed after that single assignment.

One sentence to remember: a blank final must be assigned exactly once before use – for locals before the first read, for fields in every constructor.

---

Share
Use ← → arrow keys to navigate