Java
Exception Handling
Null Reference
Programming
Software Development

Why do I get an exception when passing null constant but not when passing a null string reference?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This behavior is usually caused by Java overload resolution, not by null having two different meanings. The key detail is that the compiler chooses an overloaded method based on the compile-time type of the argument expression, and the literal null has no specific reference type until overload resolution gives it one.

The Classic Example: String.valueOf

A well-known case is String.valueOf. It has several overloads, including one that accepts Object and one that accepts char[].

java
public static String valueOf(Object obj)
public static String valueOf(char[] data)

Now compare these two calls:

java
1public class Demo {
2    public static void main(String[] args) {
3        String s = null;
4
5        System.out.println(String.valueOf(s));
6        System.out.println(String.valueOf(null));
7    }
8}

The first line prints null. The second line throws a NullPointerException.

That looks inconsistent until you look at which overload Java chooses.

Why String.valueOf(s) Works

When s is declared as String, the compiler knows the expression has type String. String is compatible with Object, but it is not compatible with char[]. That means the compiler selects:

java
String.valueOf(Object obj)

That overload is null-safe. Internally it behaves like this idea:

java
return (obj == null) ? "null" : obj.toString();

So a null String reference is still passed to the Object overload, and the result is the literal text null.

Why String.valueOf(null) Throws

The literal null is special. It can be assigned to any reference type, including Object and char[]. During overload resolution, Java picks the most specific applicable overload.

Between Object and char[], char[] is more specific because an array type is a subtype of Object. So this call:

java
String.valueOf(null)

is compiled as if you had written:

java
String.valueOf((char[]) null)

The char[] overload expects an array and eventually reads from it. Because the array reference is null, that path throws NullPointerException.

You can see the difference explicitly:

java
1public class Demo {
2    public static void main(String[] args) {
3        System.out.println(String.valueOf((Object) null));
4        System.out.println(String.valueOf((String) null));
5        System.out.println(String.valueOf((char[]) null));
6    }
7}

The first two calls return null as text. The last call throws.

Compile-Time Type Controls Overload Selection

The important rule is that overload selection happens at compile time. Java does not look at what the value happens to be at runtime and then decide again. It only uses the declared type of the expression.

For example:

java
1public class OverloadDemo {
2    static void print(Object value) {
3        System.out.println("Object overload");
4    }
5
6    static void print(String value) {
7        System.out.println("String overload");
8    }
9
10    public static void main(String[] args) {
11        String text = null;
12        Object ref = null;
13
14        print(text);
15        print(ref);
16        print(null);
17    }
18}

This prints:

text
String overload
Object overload
String overload

print(null) chooses the most specific overload among the applicable methods, which is print(String) here.

How To Avoid The Surprise

If you want predictable behavior, make the intended overload explicit with a cast or by assigning the value to a variable with the desired compile-time type.

java
String result1 = String.valueOf((Object) null);
String result2 = String.valueOf((String) null);

This matters whenever a Java API has overloads for unrelated or differently behaving reference types. If one overload is null-safe and another is not, the null literal may select the wrong one for your intention.

Another practical approach is to use a null check before calling the overloaded method:

java
String s = null;
String result = (s == null) ? "null" : String.valueOf(s);
System.out.println(result);

That is more verbose, but it removes overload-resolution ambiguity from the code path entirely.

A Broader Lesson About APIs

When reading a method signature list, do not assume null will go to the overload you mentally group as the general-purpose one. The compiler follows type specificity rules, not intent. This is especially relevant for overloaded factory methods, logging methods, and utility methods that accept arrays, objects, or varargs.

If a method call involving null behaves strangely, inspect the overloads first. The exception is often not about null handling at all; it is about choosing a different method than you expected.

Common Pitfalls

  • Assuming the literal null behaves the same as a variable that currently contains null.
  • Forgetting that overload resolution uses compile-time types, not runtime values.
  • Ignoring more specific overloads such as char[], String, or varargs methods.
  • Treating String.valueOf(null) as equivalent to String.valueOf((Object) null).
  • Writing overloaded APIs where different reference-type overloads handle null very differently.

Summary

  • The null literal has no fixed reference type until overload resolution chooses one.
  • Java picks the most specific applicable overload at compile time.
  • 'String.valueOf(s) with String s = null calls the Object overload and returns "null".'
  • 'String.valueOf(null) can select the char[] overload, which throws NullPointerException.'
  • Use casts or explicit variables when you need a specific overload.

Course illustration
Course illustration

All Rights Reserved.