Implemented creation of task instances at initial creation of case, and listing of task instances.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index 35a7701..496bd2d 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -144,35 +144,35 @@
           final TaskDefinition taskDefinition);
 
   @RequestMapping(
-          value = "/products/{productidentifier}/tasks/{taskdefinitionidentifier}",
+          value = "/products/{productidentifier}/tasks/{taskidentifier}",
           method = RequestMethod.GET,
           produces = MediaType.APPLICATION_JSON_VALUE,
           consumes = MediaType.APPLICATION_JSON_VALUE
   )
   TaskDefinition getTaskDefinition(
           @PathVariable("productidentifier") final String productIdentifier,
-          @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier);
+          @PathVariable("taskidentifier") final String taskDefinitionIdentifier);
 
   @RequestMapping(
-      value = "/products/{productidentifier}/tasks/{taskdefinitionidentifier}",
+      value = "/products/{productidentifier}/tasks/{taskidentifier}",
       method = RequestMethod.PUT,
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE)
   @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
   void changeTaskDefinition(
       @PathVariable("productidentifier") final String productIdentifier,
-      @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier,
+      @PathVariable("taskidentifier") final String taskDefinitionIdentifier,
       final TaskDefinition taskDefinition);
 
   @RequestMapping(
-      value = "/products/{productidentifier}/tasks/{taskdefinitionidentifier}",
+      value = "/products/{productidentifier}/tasks/{taskidentifier}",
       method = RequestMethod.DELETE,
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE)
   @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
   void deleteTaskDefinition(
       @PathVariable("productidentifier") final String productIdentifier,
-      @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier);
+      @PathVariable("taskidentifier") final String taskDefinitionIdentifier);
 
   @RequestMapping(
           value = "/products/{productidentifier}/charges/",
@@ -299,13 +299,13 @@
           consumes = MediaType.APPLICATION_JSON_VALUE
   )
   List<TaskInstance> getAllTasksForCase(@PathVariable("productidentifier") final String productIdentifier,
-                                         @PathVariable("caseidentifier") final String caseIdentifier,
-                                         @RequestParam(value = "includeExecuted", required = false) final Boolean includeExecuted);
+                                        @PathVariable("caseidentifier") final String caseIdentifier,
+                                        @RequestParam(value = "includeExecuted", required = false) final Boolean includeExecuted);
 
   @RequestMapping(
           value = "/products/{productidentifier}/cases/{caseidentifier}/tasks/{taskidentifier}",
           method = RequestMethod.GET,
-          produces = MediaType.ALL_VALUE,
+          produces = MediaType.APPLICATION_JSON_VALUE,
           consumes = MediaType.APPLICATION_JSON_VALUE
   )
   TaskInstance getTaskForCase(@PathVariable("productidentifier") final String productIdentifier,
@@ -313,14 +313,26 @@
                               @PathVariable("taskidentifier") final String taskIdentifier);
 
   @RequestMapping(
+      value = "/products/{productidentifier}/cases/{caseidentifier}/tasks/{taskidentifier}",
+      method = RequestMethod.PUT,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  void changeTaskForCase(@PathVariable("productidentifier") final String productIdentifier,
+                         @PathVariable("caseidentifier") final String caseIdentifier,
+                         @PathVariable("taskidentifier") final String taskIdentifier,
+                         final TaskInstance instance);
+
+  @RequestMapping(
           value = "/products/{productidentifier}/cases/{caseidentifier}/tasks/{taskidentifier}/executed",
           method = RequestMethod.PUT,
           produces = MediaType.ALL_VALUE,
           consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  void taskForCaseExecuted(@PathVariable("productidentifier") final String productIdentifier,
-                           @PathVariable("caseidentifier") final String caseIdentifier,
-                           @PathVariable("taskidentifier") final String taskIdentifier);
+  void markTaskExecution(@PathVariable("productidentifier") final String productIdentifier,
+                         @PathVariable("caseidentifier") final String caseIdentifier,
+                         @PathVariable("taskidentifier") final String taskIdentifier,
+                         final Boolean executed);
 
   @RequestMapping(
           value = "/cases/",
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/TaskInstance.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/TaskInstance.java
index 5b2aadf..9e0de90 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/TaskInstance.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/TaskInstance.java
@@ -16,39 +16,41 @@
 package io.mifos.portfolio.api.v1.domain;
 
 import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+import org.hibernate.validator.constraints.Length;
 
-import javax.validation.constraints.NotNull;
+import java.util.Objects;
 
 /**
  * @author Myrle Krantz
  */
 @SuppressWarnings({"WeakerAccess", "unused"})
 public final class TaskInstance {
-  @NotNull
-  private TaskDefinition taskDefinition;
+  @ValidIdentifier
+  private String taskIdentifier;
 
+  @Length(max = 4096)
   private String comment;
+
   private String executedOn;
 
-  @ValidIdentifier
   private String executedBy;
 
   public TaskInstance() {
   }
 
-  public TaskInstance(TaskDefinition taskDefinition, String comment, String executedOn, String executedBy) {
-    this.taskDefinition = taskDefinition;
+  public TaskInstance(String taskIdentifier, String comment, String executedOn, String executedBy) {
+    this.taskIdentifier = taskIdentifier;
     this.comment = comment;
     this.executedOn = executedOn;
     this.executedBy = executedBy;
   }
 
-  public TaskDefinition getTaskDefinition() {
-    return taskDefinition;
+  public String getTaskIdentifier() {
+    return taskIdentifier;
   }
 
-  public void setTaskDefinition(TaskDefinition taskDefinition) {
-    this.taskDefinition = taskDefinition;
+  public void setTaskIdentifier(String taskIdentifier) {
+    this.taskIdentifier = taskIdentifier;
   }
 
   public String getComment() {
@@ -79,29 +81,25 @@
   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
-
     TaskInstance that = (TaskInstance) o;
-
-    return taskDefinition != null ? taskDefinition.equals(that.taskDefinition) : that.taskDefinition == null && (comment != null ? comment.equals(that.comment) : that.comment == null && (executedOn != null ? executedOn.equals(that.executedOn) : that.executedOn == null && (executedBy != null ? executedBy.equals(that.executedBy) : that.executedBy == null)));
-
+    return Objects.equals(taskIdentifier, that.taskIdentifier) &&
+        Objects.equals(comment, that.comment) &&
+        Objects.equals(executedOn, that.executedOn) &&
+        Objects.equals(executedBy, that.executedBy);
   }
 
   @Override
   public int hashCode() {
-    int result = taskDefinition != null ? taskDefinition.hashCode() : 0;
-    result = 31 * result + (comment != null ? comment.hashCode() : 0);
-    result = 31 * result + (executedOn != null ? executedOn.hashCode() : 0);
-    result = 31 * result + (executedBy != null ? executedBy.hashCode() : 0);
-    return result;
+    return Objects.hash(taskIdentifier, comment, executedOn, executedBy);
   }
 
   @Override
   public String toString() {
     return "TaskInstance{" +
-            "taskDefinition=" + taskDefinition +
-            ", comment='" + comment + '\'' +
-            ", executedOn='" + executedOn + '\'' +
-            ", executedBy='" + executedBy + '\'' +
-            '}';
+        "taskIdentifier='" + taskIdentifier + '\'' +
+        ", comment='" + comment + '\'' +
+        ", executedOn='" + executedOn + '\'' +
+        ", executedBy='" + executedBy + '\'' +
+        '}';
   }
 }
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/TaskInstanceTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/TaskInstanceTest.java
new file mode 100644
index 0000000..81140d7
--- /dev/null
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/TaskInstanceTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TaskInstanceTest extends ValidationTest<TaskInstance> {
+
+  public TaskInstanceTest(ValidationTestCase<TaskInstance> testCase) {
+    super(testCase);
+  }
+
+  @Override
+  protected TaskInstance createValidTestSubject() {
+    final TaskInstance ret = new TaskInstance();
+    ret.setComment("Hey how y'all doin'.");
+    ret.setTaskIdentifier("validIdentifier");
+    return ret;
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ValidationTestCase> ret = new ArrayList<>();
+
+    ret.add(new ValidationTestCase<TaskInstance>("valid")
+        .adjustment(x -> {})
+        .valid(true));
+    ret.add(new ValidationTestCase<TaskInstance>("nullIdentifier")
+        .adjustment(x -> x.setTaskIdentifier(null))
+        .valid(false));
+    ret.add(new ValidationTestCase<TaskInstance>("null Comment")
+        .adjustment(x -> x.setComment(null))
+        .valid(true));
+    ret.add(new ValidationTestCase<TaskInstance>("too long Comment")
+        .adjustment(x -> x.setComment(RandomStringUtils.random(5000)))
+        .valid(false));
+
+    return ret;
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
index 4fba15b..e7bd386 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
@@ -30,7 +30,8 @@
     TestIndividualLoans.class,
     TestPatterns.class,
     TestProducts.class,
-    TestTaskDefinitions.class
+    TestTaskDefinitions.class,
+    TestTaskInstances.class
 })
 public class TestSuite extends SuiteTestEnvironment {
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java b/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java
new file mode 100644
index 0000000..956839c
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio;
+
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.api.v1.domain.TaskDefinition;
+import io.mifos.portfolio.api.v1.domain.TaskInstance;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestTaskInstances extends AbstractPortfolioTest {
+
+  @Test
+  public void shouldListTaskInstances() throws InterruptedException {
+    final Product product = createProduct();
+    final TaskDefinition taskDefinition = createTaskDefinition(product);
+
+    enableProduct(product);
+
+    final Case customerCase = createCase(product.getIdentifier());
+
+    final List<TaskInstance> taskInstances = portfolioManager.getAllTasksForCase(product.getIdentifier(), customerCase.getIdentifier(), false);
+    Assert.assertEquals(1, taskInstances.size());
+    Assert.assertEquals(taskDefinition.getIdentifier(), taskInstances.get(0).getTaskIdentifier());
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeTaskInstanceCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeTaskInstanceCommand.java
new file mode 100644
index 0000000..e94ceb4
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeTaskInstanceCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+import io.mifos.portfolio.api.v1.domain.TaskInstance;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeTaskInstanceCommand {
+  private final String productIdentifier;
+  private final String caseIdentifier;
+  private final TaskInstance instance;
+
+  public ChangeTaskInstanceCommand(String productIdentifier, String caseIdentifier, TaskInstance instance) {
+    this.productIdentifier = productIdentifier;
+    this.caseIdentifier = caseIdentifier;
+    this.instance = instance;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public String getCaseIdentifier() {
+    return caseIdentifier;
+  }
+
+  public TaskInstance getInstance() {
+    return instance;
+  }
+
+  @Override
+  public String toString() {
+    return "ChangeTaskInstanceCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", caseIdentifier='" + caseIdentifier + '\'' +
+        ", taskIdentifier=" + instance.getTaskIdentifier() +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/ExecuteTaskInstanceCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/ExecuteTaskInstanceCommand.java
new file mode 100644
index 0000000..a1e13f7
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/ExecuteTaskInstanceCommand.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ExecuteTaskInstanceCommand {
+  private final String productIdentifier;
+  private final String caseIdentifier;
+  private final String taskIdentifier;
+  private final Boolean executed;
+
+  public ExecuteTaskInstanceCommand(String productIdentifier, String caseIdentifier, String taskIdentifier, Boolean executed) {
+    this.productIdentifier = productIdentifier;
+    this.caseIdentifier = caseIdentifier;
+    this.taskIdentifier = taskIdentifier;
+    this.executed = executed;
+  }
+
+  @Override
+  public String toString() {
+    return "ExecuteTaskInstanceCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", caseIdentifier='" + caseIdentifier + '\'' +
+        ", taskIdentifier='" + taskIdentifier + '\'' +
+        ", executed=" + executed +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
index eabd4b4..a8700c0 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/CaseCommandHandler.java
@@ -32,7 +32,8 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -43,28 +44,34 @@
   private final PatternFactoryRegistry patternFactoryRegistry;
   private final ProductRepository productRepository;
   private final CaseRepository caseRepository;
+  private final TaskDefinitionRepository taskDefinitionRepository;
 
   @Autowired
   public CaseCommandHandler(final PatternFactoryRegistry patternFactoryRegistry,
                             final ProductRepository productRepository,
-                            final CaseRepository caseRepository) {
+                            final CaseRepository caseRepository,
+                            final TaskDefinitionRepository taskDefinitionRepository) {
     super();
     this.patternFactoryRegistry = patternFactoryRegistry;
     this.productRepository = productRepository;
     this.caseRepository = caseRepository;
+    this.taskDefinitionRepository = taskDefinitionRepository;
   }
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_CASE)
   public CaseEvent process(final CreateCaseCommand createCaseCommand) {
-    //TODO: Check that all designators are assigned to existing accounts.
-    //TODO: Create accounts if necessary.
-
     final Case caseInstance = createCaseCommand.getCase();
 
+    final Stream<TaskDefinitionEntity> tasksToCreate
+        = taskDefinitionRepository.findByProductId(createCaseCommand.getCase().getProductIdentifier());
+
     final CaseEntity entity = CaseMapper.map(caseInstance);
     entity.setCurrentState(Case.State.CREATED.name());
+    entity.setTaskInstances(tasksToCreate
+        .map(taskDefinition -> instanceOfDefinition(taskDefinition, entity))
+        .collect(Collectors.toSet()));
     this.caseRepository.save(entity);
 
     getPatternFactory(caseInstance.getProductIdentifier()).persistParameters(entity.getId(), caseInstance.getParameters());
@@ -76,8 +83,7 @@
   private PatternFactory getPatternFactory(String productIdentifier) {
     return productRepository.findByIdentifier(productIdentifier)
               .map(ProductEntity::getPatternPackage)
-              .map(patternFactoryRegistry::getPatternFactoryForPackage)
-              .orElse(Optional.empty())
+              .flatMap(patternFactoryRegistry::getPatternFactoryForPackage)
               .orElseThrow(() -> new IllegalArgumentException("Case references unsupported product type."));
   }
 
@@ -99,4 +105,13 @@
 
     return new CaseEvent(instance.getProductIdentifier(), instance.getIdentifier());
   }
-}
+
+  private static TaskInstanceEntity instanceOfDefinition(final TaskDefinitionEntity definition,
+                                                         final CaseEntity customerCase) {
+    final TaskInstanceEntity ret = new TaskInstanceEntity();
+    ret.setCustomerCase(customerCase);
+    ret.setTaskDefinition(definition);
+    ret.setComment("");
+    return ret;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
index 10c8452..e6ece90 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
@@ -63,8 +63,8 @@
     ret.setIdentifier(instance.getIdentifier());
     ret.setProductIdentifier(instance.getProductIdentifier());
     ret.setAccountAssignments(instance.getAccountAssignments().stream()
-            .map(x -> CaseMapper.map(x, ret))
-            .collect(Collectors.toSet()));
+        .map(x -> CaseMapper.map(x, ret))
+        .collect(Collectors.toSet()));
     ret.setCurrentState(instance.getCurrentState());
 
     final LocalDateTime time = LocalDateTime.now(Clock.systemUTC());
@@ -111,6 +111,7 @@
 
 
     newEntity.setAccountAssignments(newAccountAssignmentEntities);
+    newEntity.setTaskInstances(oldEntity.getTaskInstances());
     return newEntity;
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/TaskInstanceMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/TaskInstanceMapper.java
new file mode 100644
index 0000000..89a899c
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/TaskInstanceMapper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.mapper;
+
+import io.mifos.core.lang.DateConverter;
+import io.mifos.portfolio.api.v1.domain.TaskInstance;
+import io.mifos.portfolio.service.internal.repository.TaskInstanceEntity;
+
+/**
+ * @author Myrle Krantz
+ */
+public interface TaskInstanceMapper {
+  static TaskInstance map(final TaskInstanceEntity from) {
+    final TaskInstance ret = new TaskInstance();
+
+    ret.setTaskIdentifier(from.getTaskDefinition().getIdentifier());
+    ret.setComment(from.getComment());
+    ret.setExecutedBy(from.getExecutedBy());
+    ret.setExecutedOn(from.getExecutedOn() == null ? null : DateConverter.toIsoString(from.getExecutedOn()));
+
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
index f86092f..83e023f 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
@@ -20,6 +20,7 @@
 import javax.annotation.Nullable;
 import javax.persistence.*;
 import java.time.LocalDateTime;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -43,6 +44,9 @@
   @OneToMany(targetEntity = CaseAccountAssignmentEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "caseEntity")
   private Set<CaseAccountAssignmentEntity> accountAssignments;
 
+  @OneToMany(targetEntity = TaskInstanceEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "customerCase")
+  private Set<TaskInstanceEntity> taskInstances;
+
   @Column(name = "current_state", nullable = false)
   private String currentState;
 
@@ -99,6 +103,14 @@
     this.accountAssignments = accountAssignments;
   }
 
+  public Set<TaskInstanceEntity> getTaskInstances() {
+    return taskInstances;
+  }
+
+  public void setTaskInstances(Set<TaskInstanceEntity> taskInstances) {
+    this.taskInstances = taskInstances;
+  }
+
   public String getCurrentState() {
     return currentState;
   }
@@ -146,4 +158,18 @@
   public void setLastModifiedBy(String lastModifiedBy) {
     this.lastModifiedBy = lastModifiedBy;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CaseEntity that = (CaseEntity) o;
+    return Objects.equals(identifier, that.identifier) &&
+        Objects.equals(productIdentifier, that.productIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(identifier, productIdentifier);
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
index d13d6fc..02ae603 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
@@ -311,13 +311,13 @@
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
-    if (o == null || !(o instanceof ProductEntity)) return false;
+    if (o == null || getClass() != o.getClass()) return false;
     ProductEntity that = (ProductEntity) o;
-    return Objects.equals(getIdentifier(), that.getIdentifier());
+    return Objects.equals(identifier, that.identifier);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(getIdentifier());
+    return Objects.hash(identifier);
   }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
index ca8e4bc..f43160d 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
@@ -125,12 +125,13 @@
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     TaskDefinitionEntity that = (TaskDefinitionEntity) o;
-    return Objects.equals(id, that.id);
+    return Objects.equals(identifier, that.identifier) &&
+        Objects.equals(product, that.product);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(id);
+    return Objects.hash(identifier, product);
   }
 
   @Override
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionRepository.java
index 7c44576..6aec847 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionRepository.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionRepository.java
@@ -20,8 +20,8 @@
 import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
-import java.util.List;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -30,7 +30,7 @@
 public interface TaskDefinitionRepository extends JpaRepository<TaskDefinitionEntity, Long> {
   @SuppressWarnings("JpaQlInspection")
   @Query("SELECT t FROM TaskDefinitionEntity t WHERE t.product.identifier = :productIdentifier")
-  List<TaskDefinitionEntity> findByProductId(@Param("productIdentifier") String productId);
+  Stream<TaskDefinitionEntity> findByProductId(@Param("productIdentifier") String productId);
 
   @SuppressWarnings("JpaQlInspection")
   @Query("SELECT t FROM TaskDefinitionEntity t WHERE t.product.identifier = :productIdentifier AND t.identifier = :taskDefinitionIdentifier")
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceEntity.java
new file mode 100644
index 0000000..8ea4780
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceEntity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@Entity
+@Table(name = "bastet_c_task_insts")
+public class TaskInstanceEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @ManyToOne(fetch = FetchType.LAZY, optional = false)
+  @JoinColumn(name = "case_id")
+  private CaseEntity customerCase;
+
+  @ManyToOne(fetch = FetchType.EAGER, optional = false)
+  @JoinColumn(name = "task_def_id")
+  private TaskDefinitionEntity taskDefinition;
+
+  @Column(name = "a_comment")
+  private String comment;
+
+  @Column(name = "executed_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime executedOn;
+
+  @Column(name = "executed_by")
+  private String executedBy;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public CaseEntity getCustomerCase() {
+    return customerCase;
+  }
+
+  public void setCustomerCase(CaseEntity customerCase) {
+    this.customerCase = customerCase;
+  }
+
+  public TaskDefinitionEntity getTaskDefinition() {
+    return taskDefinition;
+  }
+
+  public void setTaskDefinition(TaskDefinitionEntity taskDefinition) {
+    this.taskDefinition = taskDefinition;
+  }
+
+  public String getComment() {
+    return comment;
+  }
+
+  public void setComment(String comment) {
+    this.comment = comment;
+  }
+
+  public LocalDateTime getExecutedOn() {
+    return executedOn;
+  }
+
+  public void setExecutedOn(LocalDateTime executedOn) {
+    this.executedOn = executedOn;
+  }
+
+  public String getExecutedBy() {
+    return executedBy;
+  }
+
+  public void setExecutedBy(String executedBy) {
+    this.executedBy = executedBy;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    TaskInstanceEntity that = (TaskInstanceEntity) o;
+    return Objects.equals(customerCase, that.customerCase) &&
+        Objects.equals(taskDefinition, that.taskDefinition);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(customerCase, taskDefinition);
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceRepository.java
new file mode 100644
index 0000000..a6cf051
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskInstanceRepository.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface TaskInstanceRepository extends JpaRepository<TaskInstanceEntity, Long> {
+  @SuppressWarnings("JpaQlInspection")
+  @Query("SELECT t FROM TaskInstanceEntity t WHERE t.taskDefinition.product.identifier = :productIdentifier AND t.customerCase.identifier = :caseIdentifier")
+  Stream<TaskInstanceEntity> findByProductIdAndCaseId(@Param("productIdentifier") String productId, @Param("caseIdentifier") String caseId);
+
+  @SuppressWarnings("JpaQlInspection")
+  @Query("SELECT t FROM TaskInstanceEntity t WHERE t.taskDefinition.product.identifier = :productIdentifier AND t.customerCase.identifier = :caseIdentifier AND t.executedOn = NULL")
+  Stream<TaskInstanceEntity> findByProductIdAndCaseIdAndExcludeExecuted(@Param("productIdentifier") String productId, @Param("caseIdentifier") String caseId);
+
+  @SuppressWarnings("JpaQlInspection")
+  @Query("SELECT t FROM TaskInstanceEntity t WHERE t.taskDefinition.product.identifier = :productIdentifier AND t.customerCase.identifier = :caseIdentifier AND t.taskDefinition.identifier = :taskIdentifier")
+  Optional<TaskInstanceEntity> findByProductIdAndCaseIdAndTaskId(@Param("productIdentifier") String productId, @Param("caseIdentifier") String caseId, @Param("taskIdentifier") String taskId);
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskDefinitionService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskDefinitionService.java
index 74ff6b5..40e13d2 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskDefinitionService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskDefinitionService.java
@@ -16,18 +16,14 @@
 package io.mifos.portfolio.service.internal.service;
 
 import io.mifos.portfolio.api.v1.domain.TaskDefinition;
-import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.service.internal.mapper.TaskDefinitionMapper;
 import io.mifos.portfolio.service.internal.repository.TaskDefinitionRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.AbstractMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -43,27 +39,11 @@
   }
 
   public List<TaskDefinition> findAllEntities(final String productIdentifier) {
-    return taskDefinitionRepository.findByProductId(productIdentifier).stream()
+    return taskDefinitionRepository.findByProductId(productIdentifier)
             .map(TaskDefinitionMapper::map)
             .collect(Collectors.toList());
   }
 
-  public Map<Action, List<TaskDefinition>> getTaskDefinitionsMappedByAction(
-          final String productIdentifier)
-  {
-    final List<TaskDefinition> taskDefinitions = findAllEntities(productIdentifier);
-
-    return taskDefinitions.stream().flatMap(this::createMappingsForTaskDefinition)
-                    .collect(Collectors.groupingBy(Map.Entry::getKey,
-                            Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
-  }
-
-  private Stream<AbstractMap.SimpleEntry<Action, TaskDefinition>> createMappingsForTaskDefinition(
-          final TaskDefinition taskDefinition)
-  {
-    return taskDefinition.getActions().stream().map(x -> new AbstractMap.SimpleEntry<>(Action.valueOf(x), taskDefinition));
-  }
-
   public Optional<TaskDefinition> findByIdentifier(final String productIdentifier, final String identifier) {
     return taskDefinitionRepository
             .findByProductIdAndTaskIdentifier(productIdentifier, identifier)
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskInstanceService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskInstanceService.java
new file mode 100644
index 0000000..37cfff1
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/TaskInstanceService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.service;
+
+import io.mifos.portfolio.api.v1.domain.TaskInstance;
+import io.mifos.portfolio.service.internal.mapper.TaskInstanceMapper;
+import io.mifos.portfolio.service.internal.repository.TaskInstanceEntity;
+import io.mifos.portfolio.service.internal.repository.TaskInstanceRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class TaskInstanceService {
+  private final TaskInstanceRepository taskInstanceRepository;
+
+  @Autowired
+  public TaskInstanceService(final TaskInstanceRepository taskInstanceRepository) {
+    this.taskInstanceRepository = taskInstanceRepository;
+  }
+
+  public List<TaskInstance> findAllEntities(final String productIdentifier,
+                                            final String caseIdentifier,
+                                            final Boolean includeExecuted) {
+    final Stream<TaskInstanceEntity> ret;
+    if (includeExecuted)
+      ret = taskInstanceRepository.findByProductIdAndCaseId(productIdentifier, caseIdentifier);
+    else {
+      ret = taskInstanceRepository.findByProductIdAndCaseIdAndExcludeExecuted(productIdentifier, caseIdentifier);
+    }
+
+    return ret.map(TaskInstanceMapper::map)
+        .collect(Collectors.toList());
+  }
+
+  public Optional<TaskInstance> findByIdentifier(final String productIdentifier,
+                                                 final String caseIdentifier,
+                                                 final String taskIdentifier) {
+    return taskInstanceRepository.findByProductIdAndCaseIdAndTaskId(productIdentifier, caseIdentifier, taskIdentifier)
+        .map(TaskInstanceMapper::map);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/TaskDefinitionRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/TaskDefinitionRestController.java
index 18c5439..1e7397d 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/TaskDefinitionRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/TaskDefinitionRestController.java
@@ -100,14 +100,14 @@
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
   @RequestMapping(
-          value = "{taskdefinitionidentifier}",
+          value = "{taskidentifier}",
           method = RequestMethod.GET,
           consumes = MediaType.APPLICATION_JSON_VALUE,
           produces = MediaType.APPLICATION_JSON_VALUE
   )
   public @ResponseBody TaskDefinition getTaskDefinition(
           @PathVariable("productidentifier") final String productIdentifier,
-          @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier)
+          @PathVariable("taskidentifier") final String taskDefinitionIdentifier)
   {
     checkProductExists(productIdentifier);
 
@@ -117,14 +117,14 @@
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
   @RequestMapping(
-      value = "{taskdefinitionidentifier}",
+      value = "{taskidentifier}",
       method = RequestMethod.PUT,
       consumes = MediaType.ALL_VALUE,
       produces = MediaType.APPLICATION_JSON_VALUE
   )
   public @ResponseBody ResponseEntity<Void> changeTaskDefinition(
           @PathVariable("productidentifier") final String productIdentifier,
-          @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier,
+          @PathVariable("taskidentifier") final String taskDefinitionIdentifier,
           @RequestBody @Valid final TaskDefinition instance)
   {
     checkTaskDefinitionExists(productIdentifier, taskDefinitionIdentifier);
@@ -141,14 +141,14 @@
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
   @RequestMapping(
-      value = "/{taskdefinitionidentifier}",
+      value = "/{taskidentifier}",
       method = RequestMethod.DELETE,
       consumes = MediaType.ALL_VALUE,
       produces = MediaType.APPLICATION_JSON_VALUE
   )
   public @ResponseBody ResponseEntity<Void> deleteTaskDefinition(
       @PathVariable("productidentifier") final String productIdentifier,
-      @PathVariable("taskdefinitionidentifier") final String taskDefinitionIdentifier
+      @PathVariable("taskidentifier") final String taskDefinitionIdentifier
   )
   {
     checkTaskDefinitionExists(productIdentifier, taskDefinitionIdentifier);
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/TaskInstanceRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/TaskInstanceRestController.java
new file mode 100644
index 0000000..2180b7e
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/TaskInstanceRestController.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.PermittableGroupIds;
+import io.mifos.portfolio.api.v1.domain.TaskInstance;
+import io.mifos.portfolio.service.internal.command.ChangeTaskInstanceCommand;
+import io.mifos.portfolio.service.internal.command.ExecuteTaskInstanceCommand;
+import io.mifos.portfolio.service.internal.service.CaseService;
+import io.mifos.portfolio.service.internal.service.TaskDefinitionService;
+import io.mifos.portfolio.service.internal.service.TaskInstanceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@RestController
+@RequestMapping("/products/{productidentifier}/cases/{caseidentifier}/tasks/")
+public class TaskInstanceRestController {
+  private final CommandGateway commandGateway;
+  private final TaskDefinitionService taskDefinitionService;
+  private final TaskInstanceService taskInstanceService;
+  private final CaseService caseService;
+
+  @Autowired
+  public TaskInstanceRestController(
+      final CommandGateway commandGateway,
+      final TaskDefinitionService taskDefinitionService,
+      final TaskInstanceService taskInstanceService,
+      final CaseService caseService)
+  {
+    super();
+    this.commandGateway = commandGateway;
+    this.taskDefinitionService = taskDefinitionService;
+    this.taskInstanceService = taskInstanceService;
+    this.caseService = caseService;
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
+  @RequestMapping(
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  List<TaskInstance> getAllTasksForCase(@PathVariable("productidentifier") final String productIdentifier,
+                                        @PathVariable("caseidentifier") final String caseIdentifier,
+                                        @RequestParam(value = "includeExecuted", required = false) final Boolean includeExecuted)
+  {
+    checkCaseExists(productIdentifier, caseIdentifier);
+
+    return taskInstanceService.findAllEntities(productIdentifier, caseIdentifier, includeExecuted);
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
+  @RequestMapping(
+      value = "{taskidentifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  TaskInstance getTaskForCase(@PathVariable("productidentifier") final String productIdentifier,
+                 @PathVariable("caseidentifier") final String caseIdentifier,
+                 @PathVariable("taskidentifier") final String taskIdentifier)
+  {
+    checkCaseExists(productIdentifier, caseIdentifier);
+
+    return taskInstanceService.findByIdentifier(productIdentifier, caseIdentifier, taskIdentifier).orElseThrow(
+        () -> ServiceException.notFound("No task instance ''{0}.{1}.{2}'' found.", productIdentifier, caseIdentifier, taskIdentifier));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
+  @RequestMapping(
+      value = "{taskidentifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> changeTaskForCase(@PathVariable("productidentifier") final String productIdentifier,
+                                         @PathVariable("caseidentifier") final String caseIdentifier,
+                                         @PathVariable("taskidentifier") final String taskIdentifier,
+                                         @RequestBody @Valid final TaskInstance instance)
+  {
+    checkCaseExists(productIdentifier, caseIdentifier);
+
+    checkTaskDefinitionExists(productIdentifier, taskIdentifier);
+
+    if (!taskIdentifier.equals(instance.getTaskIdentifier()))
+      throw ServiceException.badRequest("Instance identifiers may not be changed.");
+
+    commandGateway.process(new ChangeTaskInstanceCommand(productIdentifier, caseIdentifier, instance));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
+  @RequestMapping(
+      value = "{taskidentifier}/executed",
+      method = RequestMethod.PUT,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> markTaskExecution(@PathVariable("productidentifier") final String productIdentifier,
+                                         @PathVariable("caseidentifier") final String caseIdentifier,
+                                         @PathVariable("taskidentifier") final String taskIdentifier,
+                                         @RequestBody @Valid final Boolean executed)
+  {
+    checkTaskDefinitionExists(productIdentifier, taskIdentifier);
+
+    commandGateway.process(new ExecuteTaskInstanceCommand(productIdentifier, caseIdentifier, taskIdentifier, executed));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  private void checkTaskDefinitionExists(final String productIdentifier,
+                                         final String taskDefinitionIdentifier) throws ServiceException {
+    taskDefinitionService.findByIdentifier(productIdentifier, taskDefinitionIdentifier)
+        .orElseThrow(() -> ServiceException.notFound("No task with the identifier ''{0}.{1}'' exists.", productIdentifier, taskDefinitionIdentifier));
+  }
+
+  private void checkCaseExists(final String productIdentifier, final String caseIdentifier) throws ServiceException {
+    caseService.findByIdentifier(productIdentifier, caseIdentifier)
+        .orElseThrow(() -> ServiceException.notFound("Case ''{0}.{2}'' does not exist.", productIdentifier, caseIdentifier));
+  }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V3__task_instances.sql b/service/src/main/resources/db/migrations/mariadb/V3__task_instances.sql
new file mode 100644
index 0000000..961d820
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V3__task_instances.sql
@@ -0,0 +1,28 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE bastet_c_task_insts (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  case_id                  BIGINT         NOT NULL,
+  task_def_id              BIGINT         NOT NULL,
+  a_comment                VARCHAR(4096)  NOT NULL,
+  executed_on              TIMESTAMP(3)   NULL,
+  executed_by              VARCHAR(32)    NULL,
+  CONSTRAINT bastet_c_task_inst_pk PRIMARY KEY (id),
+  CONSTRAINT bastet_c_task_inst_uq UNIQUE (case_id, task_def_id),
+  CONSTRAINT bastet_c_task_inst_case_fk FOREIGN KEY (case_id) REFERENCES bastet_cases (id),
+  CONSTRAINT bastet_c_task_inst_def_fk FOREIGN KEY (task_def_id) REFERENCES bastet_p_task_defs (id)
+);
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/portfolio/service/internal/service/TaskDefinitionServiceTest.java b/service/src/test/java/io/mifos/portfolio/service/internal/service/TaskDefinitionServiceTest.java
index 7c83ccd..f7fb492 100644
--- a/service/src/test/java/io/mifos/portfolio/service/internal/service/TaskDefinitionServiceTest.java
+++ b/service/src/test/java/io/mifos/portfolio/service/internal/service/TaskDefinitionServiceTest.java
@@ -66,7 +66,7 @@
     final List<TaskDefinitionEntity> taskDefinitionReturn = findTaskDefinition
             ? Collections.singletonList(exampleTaskDefinition()) : Collections.emptyList();
 
-    Mockito.doReturn(taskDefinitionReturn).when(testHarness.getTaskDefinitionRepository()).findByProductId("ble");
+    Mockito.doReturn(taskDefinitionReturn.stream()).when(testHarness.getTaskDefinitionRepository()).findByProductId("ble");
 
     final List<TaskDefinition> result = testHarness.getTestSubject().findAllEntities("ble");