title: Is Jackson's @JsonSubTypes still necessary for polymorphic deserialization?
body: <p>I am able to serialize and deserialize a class hierarchy where the abstract base class is annotated with </p>
  
  <pre><code>@JsonTypeInfo(
      use = JsonTypeInfo.Id.MINIMAL_CLASS,
      include = JsonTypeInfo.As.PROPERTY,
      property = "@class")
  </code></pre>
  
  <p>but no <code>@JsonSubTypes</code> listing the subclasses, and the subclasses themselves are relatively unannotated, having only a <code>@JsonCreator</code> on the constructor.  The ObjectMapper is vanilla, and I'm not using a mixin.</p>
  
  <p>Jackson documentation on <a href="http://wiki.fasterxml.com/JacksonPolymorphicDeserialization#A2._On_type_ids">PolymorphicDeserialization and "type ids"</a> suggests (strongly) I need the <code>@JsonSubTypes</code> annotation on the abstract base class, or use it on a mixin, or that I need to <a href="http://fasterxml.github.io/jackson-databind/javadoc/2.1.1/com/fasterxml/jackson/databind/ObjectMapper.html#registerSubtypes(java.lang.Class...)">register the subtypes with the ObjectMapper</a>.  And there are plenty of SO questions and/or blog posts that agree.  Yet it works. (<em>This is Jackson 2.6.0.</em>)</p>
  
  <p>So ... am I the beneficiary of an as-yet-undocumented feature or am I relying on undocumented behavior (that may change) or is something else going on?  (I'm asking because I really don't want it to be either of the latter two.  But <a href="https://www.youtube.com/watch?v=ETN9eNOA6vw">I gots to know</a>.)</p>
  
  <p>EDIT: Adding code - and one comment.  The comment is:  I should have mentioned that all the subclasses I'm deserializing are in the same package and same jar as the base abstract class.</p>
  
  <p>Abstract base class:</p>
  
  <pre><code>package so;
  import com.fasterxml.jackson.annotation.JsonTypeInfo;
  
  @JsonTypeInfo(
      use = JsonTypeInfo.Id.MINIMAL_CLASS,
      include = JsonTypeInfo.As.PROPERTY,
      property = "@class")
  public abstract class PolyBase
  {
      public PolyBase() { }
  
      @Override
      public abstract boolean equals(Object obj);
  }
  </code></pre>
  
  <p>A subclass of it:</p>
  
  <pre><code>package so;
  import org.apache.commons.lang3.builder.EqualsBuilder;
  import com.fasterxml.jackson.annotation.JsonCreator;
  import com.fasterxml.jackson.annotation.JsonProperty;
  
  public final class SubA extends PolyBase
  {
      private final int a;
  
      @JsonCreator
      public SubA(@JsonProperty("a") int a) { this.a = a; }
  
      public int getA() { return a; }
  
      @Override
      public boolean equals(Object obj) {
          if (null == obj) return false;
          if (this == obj) return true;
          if (this.getClass() != obj.getClass()) return false;
  
          SubA rhs = (SubA) obj;
          return new EqualsBuilder().append(this.a, rhs.a).isEquals();
      }
  }
  </code></pre>
  
  <p>Subclasses <code>SubB</code> and <code>SubC</code> are the same except that field <code>a</code> is declared <code>String</code> (not <code>int</code>) in <code>SubB</code> and <code>boolean</code> (not <code>int</code>) in <code>SubC</code> (and the method <code>getA</code> is modified accordingly).</p>
  
  <p>Test class:</p>
  
  <pre><code>package so;    
  import java.io.IOException;
  import org.apache.commons.lang3.builder.EqualsBuilder;
  import org.testng.annotations.Test;
  import static org.assertj.core.api.Assertions.*;
  import com.fasterxml.jackson.annotation.JsonCreator;
  import com.fasterxml.jackson.annotation.JsonProperty;
  import com.fasterxml.jackson.databind.ObjectMapper;
  
  public class TestPoly
  {
      public static class TestClass
      {
          public PolyBase pb1, pb2, pb3;
  
          @JsonCreator
          public TestClass(@JsonProperty("pb1") PolyBase pb1,
                           @JsonProperty("pb2") PolyBase pb2,
                           @JsonProperty("pb3") PolyBase pb3)
          {
              this.pb1 = pb1;
              this.pb2 = pb2;
              this.pb3 = pb3;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (null == obj) return false;
              if (this == obj) return true;
              if (this.getClass() != obj.getClass()) return false;
  
              TestClass rhs = (TestClass) obj;
              return new EqualsBuilder().append(pb1, rhs.pb1)
                                        .append(pb2, rhs.pb2)
                                        .append(pb3, rhs.pb3)
                                        .isEquals();
          }
      }
  
      @Test
      public void jackson_should_or_should_not_deserialize_without_JsonSubTypes() {
  
          // Arrange
          PolyBase pb1 = new SubA(5), pb2 = new SubB("foobar"), pb3 = new SubC(true);
          TestClass sut = new TestClass(pb1, pb2, pb3);
  
          ObjectMapper mapper = new ObjectMapper();
  
          // Act
          String actual1 = null;
          TestClass actual2 = null;
  
          try {
              actual1 = mapper.writeValueAsString(sut);
          } catch (IOException e) {
              fail("didn't serialize", e);
          }
  
          try {
              actual2 = mapper.readValue(actual1, TestClass.class);
          } catch (IOException e) {
              fail("didn't deserialize", e);
          }
  
          // Assert
          assertThat(actual2).isEqualTo(sut);
      }
  }
  </code></pre>
  
  <p>This test passes and if you break at the second <code>try {</code> line you can inspect <code>actual1</code> and see:</p>
  
  <pre><code>{"pb1":{"@class":".SubA","a":5},
   "pb2":{"@class":".SubB","a":"foobar"},
   "pb3":{"@class":".SubC","a":true}}
  </code></pre>
  
  <p>So the three subclasses got properly serialized (each with their class name as id) and then deserialized, and the result compared equal (each subclass has a "value type" <code>equals()</code>).</p>