Last Updated: 

Why Does Hibernate Return a List of Nulls When the Executed SQL Returns Values?

Hibernate, a popular ORM (Object-Relational Mapping) framework, simplifies database interactions by mapping Java objects to database tables. However, developers often encounter a perplexing issue: when executing a query, Hibernate logs the generated SQL, and running that SQL manually returns valid rows. Yet, the list returned by Hibernate contains only null values. This discrepancy can be frustrating, but it’s almost always caused by specific mapping or configuration issues.

In this blog, we’ll demystify why Hibernate returns a list of nulls despite the underlying SQL returning data. We’ll explore common root causes, provide concrete examples, and walk through solutions to resolve each scenario.

Table of Contents#

  1. Incorrect Entity Mapping

    • Mismatched Column Names
    • Missing Column Mappings
  2. Missing or Invalid @Id Annotation

    • Missing @Id
    • Null Values in the @Id Column
  3. Projection Queries Without Proper Mapping

    • Selecting Specific Columns (Not Entities)
    • Incorrect Casting of Projection Results
  4. Native Queries With Misconfigured Result Mapping

    • Missing resultClass or @SqlResultSetMapping
  5. DTO Projections With Mismatched Constructors

  6. Conclusion

  7. References

1. Incorrect Entity Mapping#

Hibernate relies on entity mappings to map database columns to Java object fields. If these mappings are incorrect, Hibernate cannot populate entity fields, even if the SQL returns data.

Mismatched Column Names#

Problem#

The most common mapping issue is a mismatch between the column name specified in the entity and the actual column name in the database.

Example#

Suppose we have a User entity with a username field mapped to a column named usr_name, but the actual database column is user_name:

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
 
    @Column(name = "usr_name") // Incorrect: DB column is "user_name"
    private String username;
 
    // Getters and setters
}

When we run a query like:

List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();

Hibernate generates SQL like:

SELECT u.id AS id1_0_, u.usr_name AS usr_name2_0_ FROM users u;

If we run this SQL manually, the usr_name column does not exist (since the DB has user_name), so the result set for usr_name is null. However, the user might mistakenly check a different SQL (e.g., SELECT * FROM users) and see data, leading to confusion.

Explanation#

Hibernate uses the @Column(name = "...") value to generate SQL column names. If this name does not match the actual database column, the SQL result set will lack the expected column, and Hibernate will set the corresponding entity field to null. In extreme cases, if all mapped columns are mismatched, the entire entity instance may be null (though this is rare—usually, the entity is instantiated with null fields).

Solution#

Ensure the @Column(name = "...") value matches the database column name:

@Column(name = "user_name") // Correct: Matches DB column
private String username;

Missing Column Mappings#

Problem#

If an entity field lacks a @Column annotation and the field name does not match the database column name, Hibernate will use the field name as the column name by default. If the DB column name differs, the mapping fails.

Example#

Entity field userAge (camelCase) with no @Column, but the DB column is user_age (snake_case):

@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    private String username;
    private int userAge; // Hibernate assumes column "userAge" (no @Column)
 
    // Getters and setters
}

Hibernate generates SQL:

SELECT u.id AS id1_0_, u.username AS username2_0_, u.userAge AS userAge3_0_ FROM users u;

If the DB column is user_age, the userAge column in the SQL result will be null, leading to userAge being null in the entity.

Solution#

Add @Column(name = "user_age") to explicitly map the field to the DB column:

@Column(name = "user_age")
private int userAge;

2. Missing or Invalid @Id Annotation#

Hibernate requires every entity to have an identifier property annotated with @Id (or @EmbeddedId for composite IDs). Without a valid identifier, Hibernate cannot construct entity instances, resulting in null entries in the list.

Missing @Id#

Problem#

An entity without an @Id annotation will cause Hibernate to fail when constructing instances, even if the SQL returns rows.

Example#

Entity User missing @Id:

@Entity
@Table(name = "users")
public class User {
    private String username; // No @Id!
    private int age;
 
    // Getters and setters
}

Query:

List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();

What Happens#

Hibernate generates valid SQL (e.g., SELECT u.username AS username1_0_, u.age AS age2_0_ FROM users u), which returns rows when run manually. However, since there’s no @Id, Hibernate cannot uniquely identify entities or construct User instances. The result is a list with null elements, one for each row in the SQL result set.

Solution#

Add an @Id annotation to an identifier property (e.g., a primary key column):

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Now has @Id
    private String username;
    private int age;
 
    // Getters and setters
}

Null Values in the @Id Column#

Problem#

Even with @Id, if the SQL result set contains null values for the @Id column, Hibernate will return null for those entities.

Example#

Entity User with @Id mapped to id, but the SQL query returns null for id (e.g., due to a LEFT JOIN with a missing related record):

@Entity
public class User {
    @Id
    private Long id;
    private String username;
}

Query with LEFT JOIN that returns null for id:

List<User> users = entityManager.createNativeQuery(
    "SELECT u.id, u.username FROM users u LEFT JOIN inactive_users iu ON u.id = iu.user_id", 
    User.class
).getResultList();

Explanation#

Hibernate uses the @Id column to uniquely identify entities. If the @Id value is null, Hibernate cannot construct a valid entity, so it returns null for that row.

Solution#

Ensure the @Id column in the SQL result set never contains null values. Fix the query (e.g., use INNER JOIN instead of LEFT JOIN if missing records are not expected) or handle null IDs in the application logic.

3. Projection Queries Without Proper Mapping#

Projection queries (e.g., selecting specific columns instead of entire entities) require explicit mapping. If mishandled, they can return nulls or unexpected types.

Selecting Specific Columns (Not Entities)#

Problem#

Developers often write HQL/JPQL queries to select specific columns but mistakenly expect a list of entities. Hibernate returns Object arrays (or scalars) for projections, and casting these to entities results in nulls or exceptions.

Example#

Query selecting only username but expecting User entities:

// Incorrect: Selects username (String), but expects List<User>
List<User> users = entityManager.createQuery(
    "SELECT u.username FROM User u", 
    User.class
).getResultList();

What Happens#

Hibernate returns a List<String> (one String per username), not List<User>. Casting this to List<User> will either throw a ClassCastException or, if suppressed, result in null when accessing elements (since String cannot be cast to User).

Solution#

Use one of these approaches for projections:

  1. Return Object arrays:

    List<Object[]> results = entityManager.createQuery("SELECT u.id, u.username FROM User u").getResultList();
    for (Object[] row : results) {
        Long id = (Long) row[0];
        String username = (String) row[1];
    }
  2. DTO Projection with Constructor Expressions:

    public class UserDTO {
        private Long id;
        private String username;
     
        public UserDTO(Long id, String username) {
            this.id = id;
            this.username = username;
        }
     
        // Getters
    }
     
    List<UserDTO> users = entityManager.createQuery(
        "SELECT new com.example.UserDTO(u.id, u.username) FROM User u", 
        UserDTO.class
    ).getResultList();

4. Native Queries With Misconfigured Result Mapping#

Native SQL queries require explicit result mapping (via resultClass or @SqlResultSetMapping) to return entities. Without this, Hibernate returns Object arrays, and casting to entities causes nulls.

Missing resultClass or @SqlResultSetMapping#

Problem#

Using createNativeQuery without specifying the entity class or a result mapping leads to Object arrays instead of entities.

Example#

Native query missing resultClass:

// Incorrect: No resultClass, returns List<Object[]>
List<User> users = entityManager.createNativeQuery(
    "SELECT id, username FROM users"
).getResultList();

What Happens#

Hibernate returns a List<Object[]>, where each array contains column values (e.g., [1, "alice"]). Casting this to List<User> results in ClassCastException or null when accessing elements.

Solution#

Specify the entity class in createNativeQuery:

// Correct: Explicitly maps to User.class
List<User> users = entityManager.createNativeQuery(
    "SELECT id, username FROM users", 
    User.class // Result class specified
).getResultList();

For complex projections, use @SqlResultSetMapping:

@SqlResultSetMapping(
    name = "UserDTOMapping",
    classes = @ConstructorResult(
        targetClass = UserDTO.class,
        columns = {
            @ColumnResult(name = "id", type = Long.class),
            @ColumnResult(name = "username", type = String.class)
        }
    )
)
 
// Use the mapping in the query
List<UserDTO> users = entityManager.createNativeQuery(
    "SELECT id, username FROM users", 
    "UserDTOMapping"
).getResultList();

5. DTO Projections With Mismatched Constructors#

When using DTO projections with constructor expressions, mismatched parameter types or order causes Hibernate to return nulls.

Example#

DTO UserDTO has a constructor with (Long id, String username), but the query selects (username, id) (reverse order):

public class UserDTO {
    private Long id;
    private String username;
 
    // Constructor expects (id, username)
    public UserDTO(Long id, String username) {
        this.id = id;
        this.username = username;
    }
}
 
// Query selects (username, id) instead of (id, username)
List<UserDTO> users = entityManager.createQuery(
    "SELECT new com.example.UserDTO(u.username, u.id) FROM User u", // Mismatched order
    UserDTO.class
).getResultList();

Explanation#

Hibernate attempts to pass the projected values to the DTO constructor. If the parameter order/types do not match, the constructor may throw an exception, or Hibernate may return null for the DTO instance.

Solution#

Ensure the query projection order and types match the DTO constructor:

// Correct: Projection order matches constructor (id, username)
List<UserDTO> users = entityManager.createQuery(
    "SELECT new com.example.UserDTO(u.id, u.username) FROM User u", 
    UserDTO.class
).getResultList();

Conclusion#

Hibernate returning a list of nulls despite valid SQL results is almost always due to mapping or configuration issues. Key takeaways:

  • Check entity mappings: Ensure @Column names match the database and @Id is present.
  • Validate @Id values: Avoid null in @Id columns.
  • Handle projections carefully: Use DTOs or Object arrays for partial column selections.
  • Map native queries explicitly: Specify resultClass or @SqlResultSetMapping for native SQL.

By addressing these areas, you can resolve the "null list" issue and ensure Hibernate correctly maps database results to Java objects.

References#