fix: @Length with each: true on string[] reports wrong constraint in error message

Author: jdespatisCreated Sep 1, 2026Updated Sep 1, 2026
Labelstype: fixstatus: needs triage

Description

When using @Length(min, max, { each: true }) on a string[] property, the error message does not always reflect the constraint that was actually violated. In the example below, passing a value that is too long results in a message saying the value is too short

Steps to Reproduce

typescript
  import { Length, validate } from 'class-validator';

  class Dto {
    @Length(3, 4, { each: true })
    tags: string[];
  }

  const dto = new Dto();
  dto.tags = ['ABCDE']; // 5 chars — longer than the max of 4

  validate(dto).then(errors => {
    console.log(errors[0].constraints);
  });

Expected behavior

{ isLength: 'each value in tags must be shorter than or equal to 4 characters' }

or at minimum the range message:

{ isLength: 'each value in tags must be longer than or equal to 3 and shorter than or equal to 4 characters' }

Actual behavior

{ isLength: 'each value in tags must be longer than or equal to 3 characters' }

Environment

class-validator: 0.15.1

Possible Root Cause (not certain)

Looking at the defaultMessage implementation in Length.ts, the message branch is selected based on args.value.length:

typescript
  if (isMinLength && (!args.value || args.value.length < args?.constraints[0])) {                                                                                                                                                   
    return eachPrefix + '$property must be longer than or equal to $constraint1 characters';                                                                                                                                        
  } else if (isMaxLength && args.value.length > args?.constraints[1]) {                                                                                                                                                             
    return eachPrefix + '$property must be shorter than or equal to $constraint2 characters';                                                                                                                                       
  }                                                                                                                                                                                                                                 

When each: true is used, it is possible that args.value inside buildMessage refers to the array itself rather than the individual item being validated. If that is the case, args.value.length would give the array size (e.g. 1) instead of the string length (e.g. 5), which would explain why the wrong branch fires.

I haven't been able to fully trace how buildMessage is called internally when each: true is set, so I may be missing something.

Workaround

Splitting into two separate decorators avoids the issue:

typescript
  @MinLength(3, { each: true })
  @MaxLength(4, { each: true })
  tags: string[];

Have a good day

Source: typestack/class-validator