title: Upgrading from SpringBoot 2.6.6 to 2.6.7 changed behaviour with validation ( javax.validation.ConstraintDeclarationException: HV000151 )
body: <p>Just recently upgraded from SpringBoot 2.6.6 to 2.6.7, but despite the GAV for hibernate-validator has not changed <em>( both releases use <code>org.hibernate.validator:hibernate-validator:6.2.3.Final</code> and <code>jakarta.validation:jakarta.validation-api:2.0.2</code> )</em>, I noticed a change in behaviour using the validation API.</p>
  <p>I was able to reduce it to simple test case to show the difference between 2.6.6 and 2.6.7.</p>
  <p><strong>NOTE:</strong> <em>I understand the reason why it fails and know the fix, but what I don't understand is why it only started to fail with SpringBoot 2.6.7 when Hibernate validator artifact did not change between releases.</em></p>
  <p>To demonstrate, here are the code and test cases:</p>
  <ol>
  <li>First the POM file ( using SpringBoot 2.6.6 ):</li>
  </ol>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  &lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
      xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
      &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
      &lt;parent&gt;
          &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
          &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
          &lt;version&gt;2.6.6&lt;/version&gt;
          &lt;relativePath/&gt;
      &lt;/parent&gt;
      &lt;groupId&gt;org.example&lt;/groupId&gt;
      &lt;artifactId&gt;validator&lt;/artifactId&gt;
      &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
      &lt;name&gt;demo&lt;/name&gt;
      &lt;description&gt;Demo project&lt;/description&gt;
      &lt;properties&gt;
          &lt;java.version&gt;1.8&lt;/java.version&gt;
      &lt;/properties&gt;
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
              &lt;artifactId&gt;lombok&lt;/artifactId&gt;
              &lt;optional&gt;true&lt;/optional&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
  
      &lt;build&gt;
          &lt;plugins&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                  &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
              &lt;/plugin&gt;
          &lt;/plugins&gt;
      &lt;/build&gt;
  
  &lt;/project&gt;
  </code></pre>
  <ol start="2">
  <li>Then the java interface and classes :</li>
  </ol>
  <pre><code>package org.example.demo;
  
  public interface WithNameOnCard {
  
      String getNameOnCard();
      void setNameOnCard(String nameOnCard);
  
  }
  </code></pre>
  <pre><code>package org.example.demo;
  
  import lombok.Getter;
  import lombok.Setter;
  
  import javax.validation.constraints.NotBlank;
  import javax.validation.constraints.NotNull;
  
  @Getter
  @Setter
  public class MembershipCard implements WithNameOnCard {
  
      @NotNull
      @NotBlank
      private String nameOnCard;
  
      @NotNull
      private String membershipNumber;
  
  }
  </code></pre>
  <pre><code>package org.example.demo;
  
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;
  
  @SpringBootApplication
  public class DemoApplication {
  
      public static void main(String[] args) {
          SpringApplication.run(DemoApplication.class, args);
      }
  
  }
  </code></pre>
  <ol start="3">
  <li>Finally, the test case :</li>
  </ol>
  <pre><code>package org.example.demo;
  
  import org.junit.jupiter.api.Assertions;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.api.Test;
  
  import javax.validation.ConstraintViolation;
  import javax.validation.Validation;
  import javax.validation.Validator;
  import javax.validation.ValidatorFactory;
  import java.util.Set;
  
  public class ValidatorTest {
  
      public static Validator validator;
  
      @BeforeAll
      public static void setUp() {
          ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
          validator = factory.getValidator();
      }
  
      @Test
      public void whenPropertiesEmptyThenFailValidation() {
          MembershipCard membershipCard = new MembershipCard();
  
          Set&lt;ConstraintViolation&lt;MembershipCard&gt;&gt; violations = validator.validate(membershipCard);
          Assertions.assertTrue(violations.size() &gt; 0);
      }
  
      @Test
      public void whenPropertiesNotEmptyThenFailPasses() {
          MembershipCard membershipCard = new MembershipCard();
          membershipCard.setMembershipNumber(&quot;123456&quot;);
          membershipCard.setNameOnCard(&quot;JOHN SMITH&quot;);
  
          Set&lt;ConstraintViolation&lt;MembershipCard&gt;&gt; violations = validator.validate(membershipCard);
          Assertions.assertEquals(0, violations.size());
      }
  
  }
  
  </code></pre>
  <ol start="4">
  <li>Run the test, and you get :</li>
  </ol>
  <pre><code>[INFO] -------------------------------------------------------
  [INFO]  T E S T S
  [INFO] -------------------------------------------------------
  [INFO] Running org.example.demo.ValidatorTest
  &lt; ... snipped ... &gt;
  [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.196 s - in org.example.demo.ValidatorTest
  
  </code></pre>
  <ol start="5">
  <li>Now change the POM file so that it uses SpringBoot 2.6.7 :</li>
  </ol>
  <pre><code>    &lt;parent&gt;
          &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
          &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
          &lt;version&gt;2.6.7&lt;/version&gt;
          &lt;relativePath/&gt;
      &lt;/parent&gt;
  
  </code></pre>
  <ol start="6">
  <li>Run the test, and you get :</li>
  </ol>
  <pre><code>INFO] Running org.example.demo.ValidatorTest
  &lt;... snipped ...&gt;
  [ERROR] Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.17 s &lt;&lt;&lt; FAILURE! - in org.example.demo.ValidatorTest
  [ERROR] whenPropertiesEmptyThenFailValidation  Time elapsed: 0.026 s  &lt;&lt;&lt; ERROR!
  javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method MembershipCard#setNameOnCard(String) redefines the configuration of WithNameOnCard#setNameOnCard(String).
      at org.example.demo.ValidatorTest.whenPropertiesEmptyThenFailValidation(ValidatorTest.java:27)
  
  [ERROR] whenPropertiesNotEmptyThenFailPasses  Time elapsed: 0.003 s  &lt;&lt;&lt; ERROR!
  javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method MembershipCard#setNameOnCard(String) redefines the configuration of WithNameOnCard#setNameOnCard(String).
      at org.example.demo.ValidatorTest.whenPropertiesNotEmptyThenFailPasses(ValidatorTest.java:37)
  
  [ERROR] Errors: 
  [ERROR]   ValidatorTest.whenPropertiesEmptyThenFailValidation:27 » ConstraintDeclaration
  [ERROR]   ValidatorTest.whenPropertiesNotEmptyThenFailPasses:37 » ConstraintDeclaration ...
  [INFO] 
  [ERROR] Tests run: 3, Failures: 0, Errors: 2, Skipped: 0
  
  
  </code></pre>
  <p><strong>NOTE:</strong> Like I mentioned above, I understand the reason for the error so that the class and interface must obey the Liskov substitution principle.</p>
  <p>The fixes for the error to go away are, either :</p>
  <ul>
  <li>Removing the setter method <code>setNameOnCard()</code> from the <code>WithNameOnCard</code> interface. OR</li>
  <li>Moving the validation annotations from the fields of the implementing class to the getter method of the interface. OR</li>
  <li>Moving only the <code>@NotNull</code> annotation from the <code>nameOnCard</code> field of the <code>MembershipCard</code> class to the <code>getNameOnCard()</code> method of the <code>NameOnCard</code> interface, while leaving the <code>@NotBlank</code> annotation on the <code>nameOncard</code> field in the class. <em>( This last fix I actually don't understand. What's unique about <code>@NotNull</code> vs <code>@NotBlank</code> that would cause this difference between 2.6.6 and 2.6.7 ? )</em></li>
  </ul>
  <p>What I don't understand why was this error <strong>NOT</strong> generated with SpringBoot 2.6.6 when the Hibernate validator artifact remained the same between 2.6.6 and 2.6.7.</p>
  <p>So it must be something else that is causing this change in behaviour, but I can't pinpoint to what it is.</p>