programming
software development
best practices
coding
functions

Should functions return null or an empty object?

Master System Design with Codemia

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

Introduction

Whether a function should return null or an empty object depends on what the return value means in the domain. The real question is not style but semantics: are you expressing "there is no result," or are you expressing "the result exists, but it contains no items". If you treat those two cases as the same, your API becomes harder to reason about.

Use null Only for True Absence

Return null when the value genuinely may not exist. A typical example is looking up a single entity by ID.

java
1class User {
2    final String id;
3    final String name;
4
5    User(String id, String name) {
6        this.id = id;
7        this.name = name;
8    }
9}
10
11User findUserById(String id) {
12    return database.containsKey(id) ? database.get(id) : null;
13}

In this API, null means "there is no such user." That is a meaningful state, not just an empty container.

The downside is that callers must handle it explicitly:

java
1User user = findUserById("42");
2if (user != null) {
3    System.out.println(user.name);
4}

That is acceptable when absence is part of the contract. It is a problem when callers need defensive null checks everywhere because the function design was vague.

Return Empty Collections for Zero Items

If a function returns a collection, zero items is usually not the same as "no result." In that case, returning an empty collection is almost always the better contract.

java
1import java.util.ArrayList;
2import java.util.List;
3
4List<User> findUsersByTeam(String teamId) {
5    List<User> result = queryUsers(teamId);
6    return result == null ? new ArrayList<>() : result;
7}

Now callers can iterate safely without branching on null:

java
for (User user : findUsersByTeam("engineering")) {
    System.out.println(user.name);
}

That is cleaner because the meaning is precise: the team exists as a query target, but currently no users match.

Consider an Explicit Optional Type

For single optional values, a wrapper type is often clearer than null. In modern Java, Optional makes the contract visible at the type level.

java
1import java.util.Map;
2import java.util.Optional;
3
4class UserRepository {
5    private final Map<String, User> database;
6
7    UserRepository(Map<String, User> database) {
8        this.database = database;
9    }
10
11    Optional<User> findUserById(String id) {
12        return Optional.ofNullable(database.get(id));
13    }
14}

Usage becomes explicit:

java
repository.findUserById("42")
    .ifPresent(user -> System.out.println(user.name));

This avoids ambiguous null values and makes absence part of the API rather than an undocumented convention.

Empty Objects Are Useful Only When They Mean Something

Sometimes returning an empty object is correct, but only if that object is still a valid domain value. For example, a shopping cart with zero items is still a real cart.

java
1import java.util.ArrayList;
2import java.util.List;
3
4class Cart {
5    final List<String> items = new ArrayList<>();
6}
7
8Cart loadCart(String userId) {
9    Cart cart = carts.get(userId);
10    return cart != null ? cart : new Cart();
11}

This works because an empty cart is meaningful. It does not mean the user was missing or the lookup failed. It means the cart exists and is currently empty.

The pattern becomes dangerous when the empty object hides an error. If a payment lookup fails because the database is unavailable, returning a blank Payment object would be misleading. That is not "empty data"; that is a failure state.

A Simple Decision Rule

Use this rule when designing the return type:

  • return an empty collection when the answer is "zero items"
  • return null or an optional type when the answer is "no such value"
  • throw an exception or return an error result when the operation failed

Those three cases should not collapse into one return value. If they do, callers cannot distinguish normal emptiness from absence or failure.

Common Pitfalls

  • Returning null for collections, which forces callers to write unnecessary null checks.
  • Returning an empty object when the function actually failed.
  • Using null without documenting whether it means "not found" or "not computed".
  • Treating an optional single value the same way as a list of zero results.
  • Picking one convention inconsistently across similar APIs in the same codebase.

Summary

  • Return null only when absence of a single value is a valid outcome.
  • Return empty collections when the result is simply "zero items".
  • Use Optional or an equivalent type when you want absence to be explicit in the type system.
  • Return empty objects only if they are genuine domain values, not disguised failures.
  • Keep absence, emptiness, and errors as separate concepts in your API design.

Course illustration
Course illustration

All Rights Reserved.