diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java
new file mode 100644
index 0000000000000000000000000000000000000000..af9ae3971aade5f75d62481c6808ed1139dc2e07
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/QuestionnaireEvaluationController.java
@@ -0,0 +1,58 @@
+package cz.muni.ics.kypo.training.adaptive.controller;
+
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.facade.QuestionnaireEvaluationFacade;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping(value = "/training-runs", produces = MediaType.APPLICATION_JSON_VALUE)
+@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*",
+        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT})
+@Api(value = "/training-runs",
+        tags = "Training runs",
+        consumes = MediaType.APPLICATION_JSON_VALUE,
+        authorizations = @Authorization(value = "bearerAuth"))
+public class QuestionnaireEvaluationController {
+
+    private final QuestionnaireEvaluationFacade questionnaireEvaluationFacade;
+
+    @Autowired
+    public QuestionnaireEvaluationController(QuestionnaireEvaluationFacade questionnaireEvaluationFacade) {
+        this.questionnaireEvaluationFacade = questionnaireEvaluationFacade;
+    }
+
+    @ApiOperation(httpMethod = "POST",
+            value = "Evaluate answers to a questionnaire phase",
+            nickname = "evaluateAnswersToQuestionnaire",
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 200, message = "Answers evaluated"),
+            @ApiResponse(code = 500, message = "Unexpected application error")
+    })
+    @PutMapping(value = "/{runId}/questionnaire-evaluation", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Void> evaluateAnswersToQuestionnaire(@ApiParam(value = "Training run ID", required = true)
+                                                              @PathVariable("runId") Long runId,
+                                                              @ApiParam(value = "Responses to questionnaire", required = true)
+                                                              @Valid @RequestBody QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        questionnaireEvaluationFacade.evaluateAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO);
+        return ResponseEntity.noContent().build();
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java
new file mode 100644
index 0000000000000000000000000000000000000000..211b904f7e0768cc2cce281416faaa17d605de1d
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswer.java
@@ -0,0 +1,32 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "question_answer")
+public class QuestionAnswer implements Serializable {
+
+    @EmbeddedId
+    private QuestionAnswerId questionAnswerId;
+
+    private String answer;
+
+    public QuestionAnswerId getQuestionAnswerId() {
+        return questionAnswerId;
+    }
+
+    public void setQuestionAnswerId(QuestionAnswerId questionAnswerId) {
+        this.questionAnswerId = questionAnswerId;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java
new file mode 100644
index 0000000000000000000000000000000000000000..d59dff5ccdf7b39773de264a7dd56601a3d582d7
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionAnswerId.java
@@ -0,0 +1,49 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.Embeddable;
+import java.io.Serializable;
+import java.util.Objects;
+
+@Embeddable
+public class QuestionAnswerId implements Serializable {
+    private Question question;
+    private Long trainingRunId;
+
+    public QuestionAnswerId() {
+    }
+
+    public QuestionAnswerId(Question question, Long trainingRunId) {
+        this.question = question;
+        this.trainingRunId = trainingRunId;
+    }
+
+    public Question getQuestion() {
+        return question;
+    }
+
+    public void setQuestion(Question question) {
+        this.question = question;
+    }
+
+    public Long getTrainingRunId() {
+        return trainingRunId;
+    }
+
+    public void setTrainingRunId(Long trainingRunId) {
+        this.trainingRunId = trainingRunId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        QuestionAnswerId that = (QuestionAnswerId) o;
+        return Objects.equals(question, that.question) &&
+                Objects.equals(trainingRunId, that.trainingRunId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(question, trainingRunId);
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfc2bf0de2ab73fc2c22897f37d35d228092bcb3
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/QuestionPhaseResult.java
@@ -0,0 +1,61 @@
+package cz.muni.ics.kypo.training.adaptive.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "question_phase_result")
+public class QuestionPhaseResult implements Serializable {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "questionPhaseResultGenerator")
+    @SequenceGenerator(name = "questionPhaseResultGenerator", sequenceName = "question_phase_result_seq")
+    @Column(name = "question_phase_result_id", nullable = false, unique = true)
+    private Long id;
+
+    private Long trainingRunId;
+    private int achievedResult;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    private QuestionPhaseRelation questionPhaseRelation;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getTrainingRunId() {
+        return trainingRunId;
+    }
+
+    public void setTrainingRunId(Long trainingRunId) {
+        this.trainingRunId = trainingRunId;
+    }
+
+    public int getAchievedResult() {
+        return achievedResult;
+    }
+
+    public void setAchievedResult(int achievedResult) {
+        this.achievedResult = achievedResult;
+    }
+
+    public QuestionPhaseRelation getQuestionPhaseRelation() {
+        return questionPhaseRelation;
+    }
+
+    public void setQuestionPhaseRelation(QuestionPhaseRelation questionPhaseRelation) {
+        this.questionPhaseRelation = questionPhaseRelation;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..772e2fef5e9ca86d9498f8ba97be45838f8f84f6
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionAnswerDTO.java
@@ -0,0 +1,31 @@
+package cz.muni.ics.kypo.training.adaptive.dto.run;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class QuestionAnswerDTO {
+
+    @ApiModelProperty(value = "ID of answered question", example = "1")
+    @NotNull(message = "ID of the answered question must not be null")
+    private Long questionId;
+
+    @ApiModelProperty(value = "Answer to the question", example = "An answer")
+    private String answer;
+
+    public Long getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Long questionId) {
+        this.questionId = questionId;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a4eecd60731d4999c60ec5c499a7aaeb7ffc64e
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/run/QuestionnairePhaseAnswersDTO.java
@@ -0,0 +1,21 @@
+package cz.muni.ics.kypo.training.adaptive.dto.run;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.Valid;
+import java.util.List;
+
+public class QuestionnairePhaseAnswersDTO {
+
+    @Valid
+    @ApiModelProperty(value = "Answers to the questionnaire provided by user", required = true)
+    private List<QuestionAnswerDTO> answers;
+
+    public List<QuestionAnswerDTO> getAnswers() {
+        return answers;
+    }
+
+    public void setAnswers(List<QuestionAnswerDTO> answers) {
+        this.answers = answers;
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd657740cb375d2aaef4ec50eb08794d848af01a
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/QuestionnaireEvaluationFacade.java
@@ -0,0 +1,25 @@
+package cz.muni.ics.kypo.training.adaptive.facade;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.service.QuestionnaireEvaluationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class QuestionnaireEvaluationFacade {
+
+    private final QuestionnaireEvaluationService questionnaireEvaluationService;
+
+    @Autowired
+    public QuestionnaireEvaluationFacade(QuestionnaireEvaluationService questionnaireEvaluationService) {
+        this.questionnaireEvaluationService = questionnaireEvaluationService;
+    }
+
+    public void evaluateAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        List<QuestionAnswer> savedAnswers = questionnaireEvaluationService.saveAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO);
+        questionnaireEvaluationService.evaluateAnswersToQuestionnaire(runId, savedAnswers);
+    }
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c5e54e8ab08423f376c53b068f9768672a78e02
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
@@ -0,0 +1,10 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface QuestionAnswerRepository extends JpaRepository<QuestionAnswer, QuestionAnswerId> {
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..86c9945d90e33876ca59d3496d20309ee5264a90
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
@@ -0,0 +1,9 @@
+package cz.muni.ics.kypo.training.adaptive.repository;
+
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface QuestionPhaseResultRepository extends JpaRepository<QuestionPhaseResult, Long> {
+}
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionPhaseRelationRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionPhaseRelationRepository.java
index e186d22fce5a3a1b569e9f3abe4eb304354a114e..25dd89460bfb525790974d306b6e32ecae5c7970 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionPhaseRelationRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionPhaseRelationRepository.java
@@ -2,8 +2,16 @@ package cz.muni.ics.kypo.training.adaptive.repository.phases;
 
 import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionPhaseRelation;
 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.List;
+import java.util.Set;
+
 @Repository
 public interface QuestionPhaseRelationRepository extends JpaRepository<QuestionPhaseRelation, Long> {
+
+    @Query("SELECT r FROM QuestionPhaseRelation r INNER JOIN r.questions q WHERE q.id IN :questionIdList")
+    List<QuestionPhaseRelation> findAllByQuestionIdList(@Param("questionIdList") Set<Long> questionIdList);
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1d70ef79527c3377621353cc11f78aded7e8ddc
--- /dev/null
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
@@ -0,0 +1,121 @@
+package cz.muni.ics.kypo.training.adaptive.service;
+
+import cz.muni.ics.kypo.training.adaptive.domain.Question;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionChoice;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseRelation;
+import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionAnswerRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseRelationRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseResultRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.QuestionRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+public class QuestionnaireEvaluationService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(QuestionnaireEvaluationService.class);
+
+    private final QuestionRepository questionRepository;
+    private final QuestionAnswerRepository questionAnswerRepository;
+    private final QuestionPhaseRelationRepository questionPhaseRelationRepository;
+    private final QuestionPhaseResultRepository questionPhaseResultRepository;
+
+    @Autowired
+    public QuestionnaireEvaluationService(QuestionRepository questionRepository, QuestionAnswerRepository questionAnswerRepository,
+                                          QuestionPhaseRelationRepository questionPhaseRelationRepository, QuestionPhaseResultRepository questionPhaseResultRepository) {
+        this.questionRepository = questionRepository;
+        this.questionAnswerRepository = questionAnswerRepository;
+        this.questionPhaseRelationRepository = questionPhaseRelationRepository;
+        this.questionPhaseResultRepository = questionPhaseResultRepository;
+    }
+
+    public List<QuestionAnswer> saveAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        List<QuestionAnswer> result = new ArrayList<>();
+        for (QuestionAnswerDTO answer : questionnairePhaseAnswersDTO.getAnswers()) {
+            Question question = questionRepository.findById(answer.getQuestionId())
+                    .orElseThrow(() -> new RuntimeException("Question was not found"));
+            // TODO throw proper exception once kypo2-training is migrated
+
+            QuestionAnswer questionAnswer = new QuestionAnswer();
+            questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, runId));
+            questionAnswer.setAnswer(answer.getAnswer());
+
+            questionAnswerRepository.save(questionAnswer);
+            result.add(questionAnswer);
+        }
+
+        return result;
+    }
+
+    public void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) {
+        if (CollectionUtils.isEmpty(answers)) {
+            return;
+        }
+
+        Set<Long> questionIdList = answers.stream()
+                .map(QuestionAnswer::getQuestionAnswerId)
+                .map(QuestionAnswerId::getQuestion)
+                .map(Question::getId)
+                .collect(Collectors.toSet());
+
+        List<QuestionPhaseRelation> questionPhaseRelations = questionPhaseRelationRepository.findAllByQuestionIdList(questionIdList);
+        for (QuestionPhaseRelation questionPhaseRelation : questionPhaseRelations) {
+            int numberOfCorrectAnswers = 0;
+            if (CollectionUtils.isEmpty(questionPhaseRelation.getQuestions())) {
+                LOG.warn("No questions found for question phase relation {}", questionPhaseRelation.getId());
+                continue;
+            }
+
+            for (Question question : questionPhaseRelation.getQuestions()) {
+                Optional<QuestionAnswer> correspondingAnswer = findCorrespondingAnswer(question.getId(), answers);
+                if (correspondingAnswer.isPresent()) {
+                    String answer = correspondingAnswer.get().getAnswer();
+                    Optional<QuestionChoice> correspondingQuestionChoice = findCorrespondingQuestionChoice(answer, question.getChoices());
+                    if (correspondingQuestionChoice.isPresent() && correspondingQuestionChoice.get().isCorrect()) {
+                        numberOfCorrectAnswers++;
+                    }
+                } else {
+                    LOG.debug("No answer found for question {}. It is assumed as a wrong answer", question.getId());
+                }
+            }
+
+            int achievedResult = numberOfCorrectAnswers * 100 / questionPhaseRelation.getQuestions().size();
+
+            QuestionPhaseResult questionPhaseResult = new QuestionPhaseResult();
+            questionPhaseResult.setAchievedResult(achievedResult);
+            questionPhaseResult.setQuestionPhaseRelation(questionPhaseRelation);
+            questionPhaseResult.setTrainingRunId(runId);
+
+            questionPhaseResultRepository.save(questionPhaseResult);
+        }
+    }
+
+    private Optional<QuestionAnswer> findCorrespondingAnswer(Long questionId, List<QuestionAnswer> answers) {
+        return answers.stream()
+                .filter(a -> Objects.equals(questionId, a.getQuestionAnswerId().getQuestion().getId()))
+                .findFirst();
+    }
+
+    private Optional<QuestionChoice> findCorrespondingQuestionChoice(String answer, List<QuestionChoice> questionChoices) {
+        return questionChoices.stream()
+                .filter(q -> Objects.equals(answer, q.getText()))
+                .findFirst();
+    }
+}