MapStruct Enum Mapping: @ValueMapping, Handling Unmapped Values & Best Practices

Mapping Java Enums between database entities, domain models, and external API DTOs is a routine necessity in enterprise applications. While MapStruct automatically maps enum constants with matching names out of the box, real-world applications often feature mismatched names, legacy string representations, and unmapped fallback values. In this guide, we will explore how to use @ValueMapping, configure @EnumMapping for string transformations, handle missing values safely, and apply production best practices.


1. Default Enum Mapping in MapStruct

By default, if the source and target enums share identical constant names, MapStruct generates a straightforward, zero-overhead switch statement or direct assignment during compilation.

// Source Enum
public enum OrderStatus {
    PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
}

// Target Enum
public enum OrderStatusDto {
    PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
}

Your mapper interface requires no extra annotations for identical enums:

import org.mapstruct.Mapper;

@Mapper(componentModel = "cdi")
public interface OrderMapper {
    OrderStatusDto toDto(OrderStatus status);
}

At compile time, MapStruct generates clean, readable Java code:

// Generated Implementation
@Override
public OrderStatusDto toDto(OrderStatus status) {
    if (status == null) {
        return null;
    }
    switch (status) {
        case PENDING: return OrderStatusDto.PENDING;
        case PROCESSING: return OrderStatusDto.PROCESSING;
        case SHIPPED: return OrderStatusDto.SHIPPED;
        case DELIVERED: return OrderStatusDto.DELIVERED;
        case CANCELLED: return OrderStatusDto.CANCELLED;
        default: throw new IllegalArgumentException("Unexpected enum constant: " + status);
    }
}

2. Explicit Value Mapping with @ValueMapping

When source and target enum constants do not match name-for-name, use the @ValueMapping annotation to explicitly define translation rules.

// Target Enum with different terminology
public enum ExternalStatusDto {
    IN_PROGRESS, COMPLETED, REJECTED, UNKNOWN
}
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;

@Mapper(componentModel = "cdi")
public interface OrderStatusMapper {

    @ValueMapping(source = "PENDING", target = "IN_PROGRESS")
    @ValueMapping(source = "PROCESSING", target = "IN_PROGRESS")
    @ValueMapping(source = "SHIPPED", target = "COMPLETED")
    @ValueMapping(source = "DELIVERED", target = "COMPLETED")
    @ValueMapping(source = "CANCELLED", target = "REJECTED")
    ExternalStatusDto toExternalDto(OrderStatus status);
}

3. Handling Unmapped Enum Values & Fallbacks

In growing applications, new constants are frequently added to source enums. If MapStruct encounters a source constant without a target match or explicit mapping rule, it triggers a compile error by default.

MapStruct provides special constants inside MappingConstants to handle fallback and unmapped scenarios gracefully:

1. MappingConstants.ANY_REMAINING

Directs all source constants that have not been explicitly mapped with @ValueMapping to a default target value:

import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;

@Mapper(componentModel = "cdi")
public interface SafeOrderMapper {

    @ValueMapping(source = "DELIVERED", target = "COMPLETED")
    @ValueMapping(source = "CANCELLED", target = "REJECTED")
    @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "IN_PROGRESS")
    ExternalStatusDto toExternalDto(OrderStatus status);
}

2. MappingConstants.ANY_UNMAPPED

Covers any constant that neither matches by name nor has an explicit @ValueMapping definition. This is useful when some constants share identical names but remaining outliers need a fallback.

3. Mapping to null or Throwing Exceptions

You can also map unmapped or invalid constants explicitly to null or force an exception:

import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;

@Mapper(componentModel = "cdi")
public interface StrictOrderMapper {

    @ValueMapping(source = "DRAFT", target = MappingConstants.NULL)
    @ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.THROW_EXCEPTION)
    ExternalStatusDto toStrictDto(OrderStatus status);
}
Best Practice: Avoid silently mapping unknown enums to null in core domain logic. Using MappingConstants.ANY_REMAINING with an explicit fallback value (e.g., UNKNOWN or OTHER) prevents unexpected NullPointerException crashes in downstream processing.

4. Automatic Name Transformations with @EnumMapping

When mapping enums whose names follow consistent naming conventions (e.g., converting UPPER_CASE Java enums to camelCase or lowercase API strings), manually writing dozens of @ValueMapping annotations is tedious. MapStruct 1.4+ introduced @EnumMapping to handle string transformations automatically.

import org.mapstruct.EnumMapping;
import org.mapstruct.Mapper;

@Mapper(componentModel = "cdi")
public interface FlexibleEnumMapper {

    // Converts 'PAID_IN_FULL' enum to 'paidInFull' string or vice versa
    @EnumMapping(nameTransformationStrategy = "case", configuration = "camel")
    String enumToCamelCase(PaymentStatus status);

    // Strips a common prefix: 'ROLE_ADMIN' -> 'ADMIN'
    @EnumMapping(nameTransformationStrategy = "stripPrefix", configuration = "ROLE_")
    String stripRolePrefix(UserRole role);
}

Supported Name Transformation Strategies:

  • case: Transforms case conventions (lower, upper, camel).
  • stripPrefix: Removes a specified prefix string from the enum constant name.
  • stripSuffix: Removes a specified suffix string from the enum constant name.
  • prefix / suffix: Appends a string to the beginning or end of the constant name.

5. Mapping Enums to Custom Fields (Codes / IDs)

In legacy or database-first systems, enums are often persisted using custom integer codes or database string keys rather than standard enum names (e.g., STATUS_CODE = 100).

To map between a custom field inside an Enum and a target type, implement default mapping methods directly in your interface:

public enum Priority {
    LOW(10), MEDIUM(20), HIGH(30);

    private final int code;

    Priority(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    public static Priority fromCode(int code) {
        for (Priority p : Priority.values()) {
            if (p.getCode() == code) return p;
        }
        throw new IllegalArgumentException("Unknown priority code: " + code);
    }
}
import org.mapstruct.Mapper;

@Mapper(componentModel = "cdi")
public interface PriorityMapper {

    default int priorityToCode(Priority priority) {
        return priority != null ? priority.getCode() : 0;
    }

    default Priority codeToPriority(int code) {
        return Priority.fromCode(code);
    }
}

6. Quick Reference & Strategy Matrix

Requirement Annotation / Constant Example Configuration
Map mismatched enum names @ValueMapping @ValueMapping(source = "A", target = "B")
Catch all remaining unmapped enums MappingConstants.ANY_REMAINING @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "UNKNOWN")
Throw exception on unknown enum MappingConstants.THROW_EXCEPTION @ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = MappingConstants.THROW_EXCEPTION)
Transform Enum to camelCase String @EnumMapping @EnumMapping(nameTransformationStrategy = "case", configuration = "camel")
Strip constant prefix (e.g. ROLE_) @EnumMapping @EnumMapping(nameTransformationStrategy = "stripPrefix", configuration = "ROLE_")

7. Frequently Asked Questions (FAQ)

What happens if a source enum is null during mapping?

By default, MapStruct checks for null at the start of the generated mapping method and returns null before executing the switch statement. You can customize this using nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT in your @Mapper configuration.

Can I map a String directly to an Enum using MapStruct?

Yes. MapStruct automatically converts a String to an Enum using Enum.valueOf(). However, if the String value does not match any constant name, a runtime IllegalArgumentException is thrown unless you provide a fallback using MappingConstants.ANY_UNMAPPED.

How does MapStruct handle case sensitivity when mapping String to Enum?

String to Enum conversion is case-sensitive by default. If your incoming JSON or database strings vary in case, combine @EnumMapping(nameTransformationStrategy = "case", configuration = "upper") with your mapping method to normalize inputs.


Conclusion

MapStruct provides robust, type-safe tooling for mapping Java enums across application layers. By mastering @ValueMapping for custom translations, leveraging MappingConstants.ANY_REMAINING for defensive programming against unmapped values, and using @EnumMapping for automated string formatting, you can write clean, maintainable mapping code that eliminates runtime surprises.


Recommended Articles

Mastering Collection Mapping with MapStruct: A Comprehensive Guide

Learn how MapStruct simplifies collection conversions between domain entities and DTOs. Customize mappings and handle null vs. empty collections efficiently.

Mastering MapStruct Compilation Errors in Enterprise Java Applications

Learn how to resolve common MapStruct compile-time errors and ensure your Maven/Gradle builds succeed. #MapStruct #Java #EnterpriseJava #CompileTimeErrors

Map Your Data Transfer Objects (DTO) with Java Records and MapStruct for Jakarta EE Applications

How to map DTO objects with MapStruct 1.6.3 in a Jakarta EE / CDI application: Java Records, entity-to-DTO mapping, REST exposure, and the correct componentModel for Jakarta EE vs Spring.

Advanced MapStruct Features: Mapping from Multiple Sources and Using Java Expressions

Advanced MapStruct 1.6.3 tutorial: mapping from multiple sources, Java expressions, @AfterMapping, nested mappings, custom methods, and common compile-time pitfalls.