title: Thymeleaf 405 Request method 'POST' not supported
body: <p>So I'm building a a basic social media application to learn lombok and thymeleaf, and I'm having issues when I try to delete a record out of mongoDB. My application can upload images just fine, but pressing the delete key will send a POST request instead of a DELETE request. </p>
  
  <p><strong>index.html</strong></p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html xmlns:th="http://www.thymeleaf.org"&gt;
  &lt;head&gt;
      &lt;meta charset="UTF-8" /&gt;
      &lt;title&gt;Spring-a-Gram&lt;/title&gt;
      &lt;link rel="stylesheet" href="/main.css" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
  
  &lt;h1&gt;Spring Boot - Social&lt;/h1&gt;
  
  &lt;div&gt;
      &lt;table&gt;
          &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Id&lt;/th&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Image&lt;/th&gt;&lt;th&gt;&lt;/th&gt;
          &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
          &lt;tr th:each="image : ${images}"&gt;
              &lt;td th:text="${image.id}" /&gt;
              &lt;td th:text="${image.name}" /&gt;
              &lt;td&gt;
                  &lt;a th:href="@{'/images/' + ${image.name} + '/raw'}"&gt;
                      &lt;img th:src="@{'/images/'+${image.name}+'/raw'}"
                           class="thumbnail" /&gt;
                  &lt;/a&gt;
              &lt;/td&gt;
              &lt;td&gt;
                  &lt;form th:method="delete"
                        th:action="@{'/images/' + ${image.name}}"&gt;
                      &lt;input type="submit" value="Delete" /&gt;
                  &lt;/form&gt;
              &lt;/td&gt;
          &lt;/tr&gt;
          &lt;/tbody&gt;
      &lt;/table&gt;
  
      &lt;form method="post" enctype="multipart/form-data"
            action="/images"&gt;
          &lt;p&gt;&lt;input type="file" name="file" /&gt;&lt;/p&gt;
          &lt;p&gt;&lt;input type="submit" value="Upload" /&gt;&lt;/p&gt;
      &lt;/form&gt;
  &lt;/div&gt;
  
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p><strong>HomeController.java</strong></p>
  
  <pre><code>package com.alexander.springsocial;
  
  import org.springframework.core.io.InputStreamResource;
  import org.springframework.http.MediaType;
  import org.springframework.http.ResponseEntity;
  import org.springframework.http.codec.multipart.FilePart;
  import org.springframework.stereotype.Controller;
  import org.springframework.ui.Model;
  import org.springframework.web.bind.annotation.*;
  import reactor.core.publisher.Flux;
  import reactor.core.publisher.Mono;
  
  import java.io.IOException;
  
  /**
   * @author Alexander Hartson
   */
  @Controller
  public class HomeController {
  
      private static final String BASE_PATH = "/images";
      private static final String FILENAME = "{filename:.+}";
  
      private final ImageService imageService;
  
      public HomeController(ImageService imageService) {
          this.imageService = imageService;
      }
  
      @GetMapping("/")
      public Mono&lt;String&gt; index(Model model) {
          model.addAttribute("images", imageService.findAllImages());
          return Mono.just("index");
      }
  
      @GetMapping(value = BASE_PATH + "/" + FILENAME + "/raw",
          produces = MediaType.IMAGE_JPEG_VALUE)
      @ResponseBody
      public Mono&lt;ResponseEntity&lt;?&gt;&gt; oneRawImage(
              @PathVariable String filename) {
          return imageService.findOneImage(filename).map(resource -&gt; {
              try {
                  return ResponseEntity.ok()
                          .contentLength(resource.contentLength())
                          .body(new InputStreamResource(
                                  resource.getInputStream()));
              } catch (IOException e) {
                  return ResponseEntity.badRequest()
                          .body("Couldn't find " + filename +
                                  " =&gt; " + e.getMessage());
              }
          });
      }
  
      @PostMapping(value = BASE_PATH)
      public Mono&lt;String&gt; createFile(@RequestPart(name = "file")
                                     Flux&lt;FilePart&gt; files) {
          return imageService.createImage(files)
                  .then(Mono.just("redirect:/"));
      }
  
      @DeleteMapping(BASE_PATH + "/" + FILENAME)
      public Mono&lt;String&gt; deleteFile(@PathVariable String filename) {
          return imageService.deleteImage(filename)
                  .then(Mono.just("redirect:/"));
      }
  }
  </code></pre>
  
  <p><strong>ImageService.java</strong></p>
  
  <pre><code>package com.alexander.springsocial;
  
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.util.UUID;
  
  import reactor.core.publisher.Flux;
  import reactor.core.publisher.Mono;
  
  import org.springframework.boot.CommandLineRunner;
  import org.springframework.context.annotation.Bean;
  import org.springframework.core.io.Resource;
  import org.springframework.core.io.ResourceLoader;
  import org.springframework.http.codec.multipart.FilePart;
  import org.springframework.stereotype.Service;
  import org.springframework.util.FileCopyUtils;
  import org.springframework.util.FileSystemUtils;
  
  /**
   * @author Alexander Hartson
   */
  @Service
  public class ImageService {
  
      public static String UPLOAD_ROOT = "upload-dir";
  
      // tag::injection[]
      private final ResourceLoader resourceLoader;
  
      private final ImageRepository imageRepository;
  
      public ImageService(ResourceLoader resourceLoader,
                          ImageRepository imageRepository) {
          this.resourceLoader = resourceLoader;
          this.imageRepository = imageRepository;
      }
      // end::injection[]
  
      // tag::1[]
      public Flux&lt;Image&gt; findAllImages() {
          return imageRepository.findAll()
                  .log("findAll");
      }
      // end::1[]
  
      public Mono&lt;Resource&gt; findOneImage(String filename) {
          return Mono.fromSupplier(() -&gt;
                  resourceLoader.getResource(
                          "file:" + UPLOAD_ROOT + "/" + filename))
                  .log("findOneImage");
      }
  
      // tag::2[]
      public Mono&lt;Void&gt; createImage(Flux&lt;FilePart&gt; files) {
          return files
                  .log("createImage-files")
                  .flatMap(file -&gt; {
                      Mono&lt;Image&gt; saveDatabaseImage = imageRepository.save(
                              new Image(
                                      UUID.randomUUID().toString(),
                                      file.filename()))
                              .log("createImage-save");
  
                      Mono&lt;Void&gt; copyFile = Mono.just(
                              Paths.get(UPLOAD_ROOT, file.filename())
                                      .toFile())
                              .log("createImage-picktarget")
                              .map(destFile -&gt; {
                                  try {
                                      destFile.createNewFile();
                                      return destFile;
                                  } catch (IOException e) {
                                      throw new RuntimeException(e);
                                  }
                              })
                              .log("createImage-newfile")
                              .flatMap(file::transferTo)
                              .log("createImage-copy");
  
                      return Mono.when(saveDatabaseImage, copyFile)
                              .log("createImage-when");
                  })
                  .log("createImage-flatMap")
                  .then()
                  .log("createImage-done");
      }
      // end::2[]
  
      // tag::3[]
      public Mono&lt;Void&gt; deleteImage(String filename) {
          Mono&lt;Void&gt; deleteDatabaseImage = imageRepository
                  .findByName(filename)
                  .log("deleteImage-find")
                  .flatMap(imageRepository::delete)
                  .log("deleteImage-record");
  
          Mono&lt;Object&gt; deleteFile = Mono.fromRunnable(() -&gt; {
              try {
                  Files.deleteIfExists(
                          Paths.get(UPLOAD_ROOT, filename));
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          })
                  .log("deleteImage-file");
  
          return Mono.when(deleteDatabaseImage, deleteFile)
                  .log("deleteImage-when")
                  .then()
                  .log("deleteImage-done");
      }
      // end::3[]
  
      /**
       * Pre-load some test images
       *
       * @return Spring Boot {@link CommandLineRunner} automatically
       *         run after app context is loaded.
       */
      @Bean
      CommandLineRunner setUp() throws IOException {
          return (args) -&gt; {
              FileSystemUtils.deleteRecursively(new File(UPLOAD_ROOT));
  
              Files.createDirectory(Paths.get(UPLOAD_ROOT));
  
              FileCopyUtils.copy("Test file",
                      new FileWriter(UPLOAD_ROOT +
                              "/image1.jpg"));
  
              FileCopyUtils.copy("Test file2",
                      new FileWriter(UPLOAD_ROOT +
                              "/image2.jpg"));
  
              FileCopyUtils.copy("Test file3",
                      new FileWriter(UPLOAD_ROOT + "/image3.jpg"));
          };
      }
  }
  </code></pre>
  
  <p><strong>Gradle</strong></p>
  
  <pre><code>buildscript {
      ext {
          springBootVersion = '2.0.4.RELEASE'
      }
      repositories {
          mavenCentral()
          maven { url "https://repo.spring.io/snapshot" }
          maven { url "https://repo.spring.io/milestone" }
      }
      dependencies {
          classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
      }
  }
  
  apply plugin: 'java'
  apply plugin: 'eclipse'
  apply plugin: 'org.springframework.boot'
  apply plugin: 'io.spring.dependency-management'
  
  group = 'com.alexander.spring-social'
  version = '0.0.1-SNAPSHOT'
  sourceCompatibility = 1.8
  
  repositories {
      mavenCentral()
      maven { url "https://repo.spring.io/snapshot" }
      maven { url "https://repo.spring.io/milestone" }
      maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
  }
  
  
  dependencies {
      // tag::netty
      compile('org.springframework.boot:spring-boot-starter-webflux') {
          exclude group: 'org.springframework.boot',
          module: 'spring-boot-starter-reactor-netty'
      }
      compile('org.springframework.boot:spring-boot-starter-tomcat')
      compile('org.springframework.boot:spring-boot-starter-thymeleaf')
      compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
      compile('org.synchronoss.cloud:nio-multipart-parser')
      // Specify lombok version
      compileOnly('org.projectlombok:lombok:1.18.2')
      testCompile('org.springframework.boot:spring-boot-starter-test')
      testCompile("io.projectreactor:reactor-test")
  }
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/uEoSs.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/uEoSs.png" alt="enter image description here"></a></p>
  
  <p>From my debugging in this screenshot, it looks like the form method is defining the action as a "post" even though I specified "delete"? It doesn't make sense to me why it's doing this. Any ideas would be appreciated. </p>