title: POST duplicate entry not causing PK collision in Spring Data REST
body: <p>As per <a href="http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_post" rel="nofollow noreferrer">Spring Data REST Documentation</a>, POST method creates a new entity from the given request body. However, I found it could also update the existing entity. In some cases, this can be problematic. Here is an example:</p>
  
  <p>DemoApplication.java</p>
  
  <pre><code>package com.example;
  
  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>
  
  <p>UserRepository.java</p>
  
  <pre><code>package com.example;
  
  import org.springframework.data.repository.PagingAndSortingRepository;
  
  public interface UserRepository extends PagingAndSortingRepository&lt;User, String&gt; {}
  </code></pre>
  
  <p>User.java</p>
  
  <pre><code>package com.example;
  
  import javax.persistence.Entity;
  import javax.persistence.Id;
  
  @Entity
  public class User {
      @Id
      private String username;
      private String password;
      public String getUsername() {
          return username;
      }
      public void setUsername(String username) {
          this.username = username;
      }
      public String getPassword() {
          return password;
      }
      public void setPassword(String password) {
          this.password = password;
      }
  }
  </code></pre>
  
  <p>pom.xml (within project tag)</p>
  
  <pre><code>&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  
  &lt;groupId&gt;com.example&lt;/groupId&gt;
  &lt;artifactId&gt;demo&lt;/artifactId&gt;
  &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
  &lt;packaging&gt;jar&lt;/packaging&gt;
  
  &lt;name&gt;demo&lt;/name&gt;
  &lt;description&gt;Demo project for Spring Boot&lt;/description&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;1.5.1.RELEASE&lt;/version&gt;
      &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
  &lt;/parent&gt;
  
  &lt;properties&gt;
      &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
      &lt;project.reporting.outputEncoding&gt;UTF-8&lt;/project.reporting.outputEncoding&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-data-jpa&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
          &lt;artifactId&gt;spring-boot-starter-data-rest&lt;/artifactId&gt;
      &lt;/dependency&gt;
  
      &lt;dependency&gt;
          &lt;groupId&gt;com.h2database&lt;/groupId&gt;
          &lt;artifactId&gt;h2&lt;/artifactId&gt;
          &lt;scope&gt;runtime&lt;/scope&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;/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;
  </code></pre>
  
  <p>application.properties: empty</p>
  
  <p>URL: <a href="http://localhost:8080/users" rel="nofollow noreferrer">http://localhost:8080/users</a></p>
  
  <p>Method: POST</p>
  
  <p>JSON content:</p>
  
  <pre><code>{"username":"user","password":"password"}
  </code></pre>
  
  <p>I assumed the above POST request was getting HTTP 201 at the first time, and only for one time. However, I was able to send the above POST request many times, and got HTTP 201 all the time. In addition, I was also able to change the password in the database using POST request.</p>
  
  <p>I believe that this is a security problem. For example, I might allow anonymous user registration through a POST request. But, with above situation, the existing user could be overwritten.</p>
  
  <blockquote>
    <p><strong>Question:</strong> How can I prevent a new entity being created from a POST request if an old entity has already existed with the same id? Or, did I miss interpret the Spring Data REST Documentation?</p>
  </blockquote>
  
  <p><strong>Supplementary explanation:</strong></p>
  
  <p>The cause of this issue is the design behind Spring Data REST. Because Spring Data REST is built upon Spring Data JPA, which was not used to directly expose to the “outside”. So it “trusts” data that comes in. The method isNew in <a href="https://github.com/spring-projects/spring-data-commons/blob/2.0.x/src/main/java/org/springframework/data/repository/core/support/AbstractEntityInformation.java" rel="nofollow noreferrer">org.springframework.data.repository.core.support.AbstractEntityInformation</a> shows how data is determined as new or not new.</p>
  
  <pre><code>public boolean isNew(T entity) {
  
      ID id = getId(entity);
      Class&lt;ID&gt; idType = getIdType();
  
      if (!idType.isPrimitive()) {
          return id == null;
      }
  
      if (id instanceof Number) {
          return ((Number) id).longValue() == 0L;
      }
  
      throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!", idType));
  }
  </code></pre>
  
  <p>The result of isNew method will eventually effects save method in <a href="https://github.com/spring-projects/spring-data-jpa/blob/2.0.x/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java" rel="nofollow noreferrer">org.springframework.data.jpa.repository.support.SimpleJpaRepository</a>.</p>
  
  <pre><code>public &lt;S extends T&gt; S save(S entity) {
  
      if (entityInformation.isNew(entity)) {
          em.persist(entity);
          return entity;
      } else {
          return em.merge(entity);
      }
  }
  </code></pre>
  
  <p>In the situation mentioned in this question, the username field, which is also the id of the user entity, would always contain data in order to create new users. Therefore, when it goes to isNew, id == null will always return false. Then save method will always perform a merge operation.</p>
  
  <p>The above hints are all I can provide. Even though, I do not know if there is a solution to solve this problem.</p>
  
  <p><em>URL links are just references. They may be not the exact the same version to what I am using.</em></p>