Skip to content
Her Tech Corner Her Tech Corner
  • Home
  • Learn Spring
    • Spring Frameworks
    • Java Core
  • Microservices
  • Cloud
  • My Journey
    • Machine Learning
    • Linux
    • Algorithm
    • Book Notes
  • Tools I use
Her Tech Corner
Her Tech Corner

Java 8 Optional: Practical Examples and Use Cases

Posted on July 24, 2023September 4, 2023 By Hoai Thu

Last updated on September 4th, 2023

Java 8 introduced the Optional feature, providing a safer way to handle potential null values and reduce the risk of NullPointerExceptions.

In this post, we’ll explore Java 8 Optional methods, practical examples and how they can improve code readability.

Table of Contents

Toggle
  • Java 8 Optional’s methods overview
  • Optional Practical Use Cases
    • Use Optional for null references handling
    • Using Optional Repository layer
    • Use Optional properties in requests for partial updates
  • Conclusion

Java 8 Optional’s methods overview

Optional is a container that holds at most one value, promoting better programming practices. Indeed, the Optional class proves valuable in wrapping our data, eliminating the need for conventional null checks and reducing try-catch blocks that deal with NullPointerExceptions.

Below is a brief introduction to some methods that can be used with the Optional class:

  • Optional.ofNullable(value) : return a non-empty Optional if a value is present in the given object; otherwise returns an empty Optional.
  • Optional.of(value) : return Optional containing the non-null value.
  • Optional.orElse(defaultValue): return the wrapped value if it’s present, or the specified defaultValue if it’s empty; defaultValue is always evaluated, even if the Optional is not empty.
  • Optional.orElseGet(supplier): Return the wrapped value if it’s present, or the value produced by the supplier function if it’s empty. The supplier function is only called if the Optional is empty.
  • Optional.orElseThrow(ExceptionSupplier): Return the wrapped value if it’s present, or throws an exception produced by the exceptionSupplier function if it’s empty.

Optional Practical Use Cases

In this part, we will explore the practical applications of Java 8’s Optional, leveraging real-life examples based on my personal experiences.

Use Optional for null references handling

Assume that I have a Student and Address class as below:


public class Student {

    public Student(Integer id) {
        this.id = id;
    }

    private Integer id;
    private Address address;

    public Address getAddress() {
        return address;
    }
}

public class Address {
    private String province;

    public String getProvince() {
        return province;
    }
}

Let’s say I have a method called getProvince() that returns a province if it exists; otherwise, it returns a string.


public class OptionalPractices {

    private String getProvince() {
        Student student = getStudent();
        if (student != null) {
            Address address = student.getAddress();
            if (address != null) {
                String province = address.getProvince();
                if (province != null) {
                    return province;
                }
            }
        }
        return "not specified";
    }

    private Student getStudent() {
        // logic to getStudent(), it might be retrieved from DB
        return new Student(1);
    }
}

This issue occurs in much of our code, where nested if statements and manual checking for null values are prevalent. In practice, we often encounter nested JSON objects, which easily lead to this problem. While the code itself functions, there are better ways to achieve the same functionality by utilizing Optional in conjunction with Map.


    private String getProvinceRefactor() {
        Student student = getStudent();
        return Optional.ofNullable(student)
                .map(Student::getAddress)
                .map(Address::getProvince)
                .orElse("not specified");
    }

The nested if is replaced by chained map() calls that access nested properties only if the Optional is not empty (no explicit null checks). Basically, a map() method applies a transformation function to the wrapped value if it’s present and returns a new Optional contain result. If the wrapped value is not present (empty), a new empty Optional is returned.

This version retains the same functionality as the original code but is easier to read, maintain and scales better.

Using Optional Repository layer

One valuable use case for Optional is in the repository layer when working with Spring Data JPA. It allows us to wrap data before returning it, especially when the data might potentially be null.


public interface LegalInfoRepository extends JpaRepository<LegalInfoEntity, Long> {
  Optional<LegalInfoEntity> findByUserEntityUserId(String userId);
}

By using Optional in this context, we indicate that the result may or may not exist, providing a clear and safe way to handle null values.

Use Optional properties in requests for partial updates

Consider an application that manages user legal information, which includes an UpdateLegalInfoRequest class. The problem of partially updating a resource is quite common, as illustrated below:


public class UpdateLegalInfoRequest {
    private String identificationNum;
    private String taxCodeNo;
}

The logic at Controller and Service:


@PostMapping(value = "/{userId}/legal-info")
void updateLegalInformation(
      @PathVariable UUID userId, @Valid @RequestBody UpdateLegalInfoRequest legalInfo);

The traditional way to handle this is by checking for null values in properties that need to be updated.


public void updateUserLegalInfo(User user, UpdateLegalInfoRequest updateRequest) {
    if (updateRequest.getIdentificationNum() != null) {
        user.setIdentificationNum(updateRequest.getIdentificationNum());
    }
    if (updateRequest.getTaxCodeNo() != null) {
        user.setTaxCodeNo(updateRequest.getTaxCodeNo());
    }
}

All good, but there are several issues with this approach:

  • It’s not evident whether a null value indicates that the property should not be updated or if the property should be updated with a null value.
  • Adding more fields or conditions complicates the code, making it harder to maintain.
  • Code duplication arises due to the repetitive null checks for each property.

Now, let’s see how Optional can help with partial updates. We’ll modify the UpdateLegalInfoRequest class to use Optional fields:


@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UpdateLegalInfoRequest {

    private static final String LENGTH_255_VALIDATION = "Length should be from 1 to 255 characters.";

    private Optional<@Length(max = 255, message = LENGTH_255_VALIDATION) String> identificationNum;

    private Optional<@Length(max = 255, message = LENGTH_255_VALIDATION) String> taxCodeNo;
}

At its service


@Override
 public void updateLegalInformation(UUID userId, UpdateLegalInfoRequest legalInfoRequest) {
    final ProfileEntity profileEntity = getUserById(userId.toString());

    LegalInfoEntity existentEntity =
        legalInfoRepository
            .findByUserEntityUserId(userId.toString())
            .orElseGet(() -> LegalInfoEntity.builder().build());
    existentEntity.setProfileEntity(profileEntity);

    LegalInfoEntity updatedEntity =
        legalInfoMapper.convertToLegalEntity(existentEntity, legalInfoRequest);

    legalInfoRepository.save(updatedEntity);
  }

Mapper using Mapstruct:


@Mapper(
    componentModel = "spring",
    uses = {JsonNullableMapper.class, DateMapper.class})
public interface LegalInfoMapper {

  @BeanMapping(
      nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
      nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
  @Mapping(
      target = "identificationNum",
      source = "legalInfoRequest.identificationNum",
      qualifiedByName = "unwrap")
	@Mapping(target = "taxCodeNo", source = "legalInfoRequest.taxCodeNo", qualifiedByName = "unwrap")
  LegalInfoEntity convertToLegalEntity(
      @MappingTarget LegalInfoEntity existentLegalInfoEntity,
      UpdateLegalInfoRequest legalInfoRequest);
}

@Mapper(componentModel = "spring")
public interface JsonNullableMapper {

  @Named("unwrap")
  default <T> T unwrap(Optional<T> optional) {
    return optional.orElse(null);
  }

  @Named("wrap")
  default <T> Optional<T> wrap(T object) {
    return Optional.of(object);
  }
}

Then, let’s do the integration test for 2 cases:

TC2: No value is provided, the property should not be updated


    @Test
    @Sql("TEST_SAMPLE_DATA")
    void givenValidRequestWithExistentLegalInfo_WhenUpdateLegalInformation_ThenUpdatedSuccessful()
            throws Exception {
        String request =
                "{\n"
                        + "    \"identificationNum\": \"IDENTIFICATION\""
                        + "}";

        mockMvc
                .perform(
                        post("/{userId}/legal-info", )
                                .contentType(APPLICATION_JSON_MEDIA_TYPE)
                                .content(request))
                .andExpect(status().isOk());

        mockMvc
                .perform(get("/{userId}/legal-info", "494f9ae8-d16a-4f21-a5ed-f25ff45d8b98"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.identificationNum").value("IDENTIFICATION"))
                // the taxCodeNo should not be updated
                .andExpect(jsonPath("$.taxCodeNo").value("123TAX"));
    }




TC2: Update null value explicitly


    @Test
    @Sql("TEST_SAMPLE_DATA")
    void givenValidRequestWithExistentLegalInfo_WhenUpdateLegalInformation_ThenUpdatedSuccessful()
            throws Exception {
        String request =
                "{\n"
                        + "    \"identificationNum\": \"IDENTIFICATION\",\n"
                        + "    \"taxCodeNo\": null\n"
                        + "}";

        mockMvc
                .perform(
                        post("/{userId}/legal-info", )
                                .contentType(APPLICATION_JSON_MEDIA_TYPE)
                                .content(request))
                .andExpect(status().isOk());

        mockMvc
                .perform(get("/{userId}/legal-info", "494f9ae8-d16a-4f21-a5ed-f25ff45d8b98"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.identificationNum").value("IDENTIFICATION"))
                // the taxCodeNo should be updated to null
                .andExpect(jsonPath("$.taxCodeNo"), CoreMatchers.is(nullValue())));
    }

Conclusion

In this post, I covered most of the important features of the Java 8 Optional class, including various methods and my practical usages in the repository, null references handling and partial updates. This knowledge will continue to evolve, and I hope it proves helpful in your coding endeavours.

Happy coding!

Related

Java Core Java8Refactoring

Post navigation

Previous post
Next post

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Categories

  • Algorithm (2)
  • Cloud (1)
  • Database (1)
  • Java Core (1)
  • Learn Spring (1)
  • Machine Learning (1)
  • Microservices (4)
  • Spring Frameworks (6)
  • Tools I use (1)

Recent Posts

  • Leetcode challenges 2023-2024December 12, 2023
  • Docker Essentials: A Novice’s JourneyOctober 7, 2023
  • Spring Cloud Gateway as an OAuth2 ClientSeptember 25, 2023
  • An Introduction to Prompt Engineering with LLMsSeptember 18, 2023
  • A brief overview of API Gateway Pattern in MicroservicesSeptember 7, 2023

Tags

Algorithms AOP API Gateway Authorization Server Cloud Database Design Pattern Docker Java8 Jwt Leetcode LLMs Microservices Oauth2 Prompt Refactoring Spring

©2025 Her Tech Corner | WordPress Theme by SuperbThemes