diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationArguments.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationArguments.java
new file mode 100644
index 0000000000000000000000000000000000000000..b19d71f5056b57626576a390c7f0a9f575e12c15
--- /dev/null
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationArguments.java
@@ -0,0 +1,64 @@
+package de.deadlocker8.budgetmaster.migration;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MigrationArguments
+{
+	static class MigrationArgumentBuilder
+	{
+		private String sourceUrl;
+		private String destinationUrl;
+		private String destinationUsername;
+		private String destinationPassword;
+
+		public MigrationArgumentBuilder withSourceUrl(String databasePath)
+		{
+			databasePath = databasePath.replace("\\", "/");
+			this.sourceUrl = MessageFormat.format("jdbc:h2:/{0}", databasePath);
+			return this;
+		}
+
+		public MigrationArgumentBuilder withDestinationUrl(String hostname, Integer port, String databaseName)
+		{
+			this.destinationUrl = MessageFormat.format("jdbc:postgresql://{0}:{1,number,#}/{2}", hostname, port, databaseName);
+			return this;
+		}
+
+		public MigrationArgumentBuilder withDestinationCredentials(String username, String password)
+		{
+			this.destinationUsername = username;
+			this.destinationPassword = password;
+			return this;
+		}
+
+		public MigrationArguments build()
+		{
+			return new MigrationArguments(sourceUrl, destinationUrl, destinationUsername, destinationPassword);
+		}
+	}
+
+	private final String sourceUrl;
+	private final String destinationUrl;
+	private final String destinationUsername;
+	private final String destinationPassword;
+
+	private MigrationArguments(String sourceUrl, String destinationUrl, String destinationUsername, String destinationPassword)
+	{
+		this.sourceUrl = sourceUrl;
+		this.destinationUrl = destinationUrl;
+		this.destinationUsername = destinationUsername;
+		this.destinationPassword = destinationPassword;
+	}
+
+	public List<String> getArguments()
+	{
+		final ArrayList<String> arguments = new ArrayList<>();
+		arguments.add(MessageFormat.format("--spring.datasource.jdbc-url={0}", sourceUrl));
+		arguments.add(MessageFormat.format("--spring.seconddatasource.jdbc-url={0}", destinationUrl));
+		arguments.add(MessageFormat.format("--spring.seconddatasource.username={0}", destinationUsername));
+		arguments.add(MessageFormat.format("--spring.seconddatasource.password={0}", destinationPassword));
+		return arguments;
+	}
+}
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java
index 59e06098bc64c9b071a73407c2e167169335ffa8..2c4da4a00043b4dfa9f6795e9aafdc622ac30c0b 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java
@@ -5,12 +5,17 @@ import de.deadlocker8.budgetmaster.controller.BaseController;
 import de.deadlocker8.budgetmaster.settings.SettingsService;
 import de.deadlocker8.budgetmaster.utils.Mappings;
 import de.deadlocker8.budgetmaster.utils.Strings;
+import de.deadlocker8.budgetmaster.utils.WebRequestUtils;
+import de.deadlocker8.budgetmaster.utils.notification.Notification;
+import de.deadlocker8.budgetmaster.utils.notification.NotificationType;
+import de.thecodelabs.utils.util.Localization;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.FieldError;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.WebRequest;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
@@ -31,12 +36,14 @@ public class MigrationController extends BaseController
 		public static final String MIGRATION_SETTINGS = "migration";
 	}
 
+	private final MigrationService migrationService;
 	private final SettingsService settingsService;
 	private final UserService userService;
 
 	@Autowired
-	public MigrationController(SettingsService settingsService, UserService userService)
+	public MigrationController(MigrationService migrationService, SettingsService settingsService, UserService userService)
 	{
+		this.migrationService = migrationService;
 		this.settingsService = settingsService;
 		this.userService = userService;
 	}
@@ -56,7 +63,8 @@ public class MigrationController extends BaseController
 	}
 
 	@PostMapping
-	public String post(Model model,
+	public String post(WebRequest request,
+					   Model model,
 					   @ModelAttribute("MigrationSettings") @Valid MigrationSettings migrationSettings, BindingResult bindingResult,
 					   @RequestParam(value = "verificationPassword") String verificationPassword)
 	{
@@ -70,14 +78,31 @@ public class MigrationController extends BaseController
 			bindingResult.addError(verificationError);
 		}
 
+		model.addAttribute(ModelAttributes.MIGRATION_SETTINGS, migrationSettings);
+
 		if(bindingResult.hasErrors())
 		{
 			model.addAttribute(ModelAttributes.ERROR, bindingResult);
-			model.addAttribute(ModelAttributes.MIGRATION_SETTINGS, migrationSettings);
 			return ReturnValues.MIGRATION_SETTINGS;
 		}
 
-		// TODO
+		try
+		{
+			final MigrationArguments migrationArguments = new MigrationArguments.MigrationArgumentBuilder()
+					.withSourceUrl(migrationService.getDatabaseFromPreviousVersionPathWithoutExtension().toString())
+					.withDestinationUrl(migrationSettings.hostname(), migrationSettings.port(), migrationSettings.databaseName())
+					.withDestinationCredentials(migrationSettings.username(), migrationSettings.password())
+					.build();
+			// TODO: run non-blocking and redirect to progress page
+			migrationService.runMigration(migrationArguments);
+		}
+		catch(MigrationException e)
+		{
+			WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.migration.error", e.getMessage()), NotificationType.ERROR));
+			return ReturnValues.MIGRATION_SETTINGS;
+		}
+
+		// TODO: redirect to success page
 		return ReturnValues.MIGRATION_SETTINGS;
 	}
 }
\ No newline at end of file
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationException.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..182d761075decc61492767e6994f6b3808d868ce
--- /dev/null
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationException.java
@@ -0,0 +1,15 @@
+package de.deadlocker8.budgetmaster.migration;
+
+public class MigrationException extends Exception
+{
+	public MigrationException(String message)
+	{
+		super(message);
+	}
+
+	public MigrationException(String message, Throwable cause)
+	{
+		super(message, cause);
+	}
+}
+
diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java
index 2b47ba27d2449f3ffbaa59b32032990a6df07bfb..32507fb555aa3bc8739c212ed397b10dfaf7f0b9 100644
--- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java
+++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java
@@ -7,16 +7,28 @@ import de.deadlocker8.budgetmaster.settings.SettingsService;
 import de.deadlocker8.budgetmaster.templates.TemplateRepository;
 import de.deadlocker8.budgetmaster.transactions.TransactionRepository;
 import de.deadlocker8.budgetmaster.utils.Mappings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
 
 @Service
 public class MigrationService
 {
+	private static final Logger LOGGER = LoggerFactory.getLogger(MigrationService.class);
+
 	public static final String PREVIOUS_DATABASE_FILE_NAME = "budgetmaster.mv.db";
+	public static final String PREVIOUS_DATABASE_FILE_NAME_WITHOUT_EXTENSION = "budgetmaster";
 	private final SettingsService settingsService;
 	private final Path applicationSupportFolder;
 	private final AccountRepository accountRepository;
@@ -76,9 +88,79 @@ public class MigrationService
 		return templateRepository.findAll().isEmpty();
 	}
 
+	private Path getDatabaseFromPreviousVersionPath()
+	{
+		return applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME);
+	}
+
+	public Path getDatabaseFromPreviousVersionPathWithoutExtension()
+	{
+		return applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME_WITHOUT_EXTENSION);
+	}
+
 	private boolean isDatabaseFromPreviousVersionExisting()
 	{
-		final Path previousDatabasePath = applicationSupportFolder.resolve(PREVIOUS_DATABASE_FILE_NAME);
-		return Files.exists(previousDatabasePath);
+		return Files.exists(getDatabaseFromPreviousVersionPath());
+	}
+
+	public String runMigration(MigrationArguments migrationArguments) throws MigrationException
+	{
+		// TODO: extract BudgetMasterMigrator.jar from resources to tmp folder
+
+		return runMigrator(migrationArguments);
+	}
+
+	private String runMigrator(MigrationArguments migrationArguments) throws MigrationException
+	{
+		final String javaCommand = determineJavaCommand();
+
+		final List<String> command = new ArrayList<>();
+		command.add(MessageFormat.format("\"{0}\"", javaCommand));
+		command.add("-jar");
+		command.add("C:/Programmierung/BudgetMaster/BudgetMasterDatabaseMigrator/target/BudgetMasterDatabaseMigrator-v2.10.0.jar");
+		command.addAll(migrationArguments.getArguments());
+		LOGGER.debug("Starting migration with command: {}", command);
+
+		try
+		{
+			final ProcessBuilder processBuilder = new ProcessBuilder(command).redirectErrorStream(true);
+			final Process process = processBuilder.start();
+
+			final StringBuilder collectedStdout = new StringBuilder();
+			try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
+			{
+				while(true)
+				{
+					String line = in.readLine();
+					if(line == null)
+					{
+						break;
+					}
+
+					LOGGER.debug("[MIGRATOR] {}", line);
+
+					collectedStdout.append(line);
+					collectedStdout.append("\n");
+				}
+			}
+
+			LOGGER.debug("Migration process finished");
+			return collectedStdout.toString();
+		}
+		catch(IOException e)
+		{
+			throw new MigrationException("Error during migration process", e);
+		}
+	}
+
+	private String determineJavaCommand() throws MigrationException
+	{
+		final Optional<String> commandResultOptional = ProcessHandle.current().info().command();
+		if(commandResultOptional.isEmpty())
+		{
+			throw new MigrationException("Could not determine java executable");
+		}
+
+		return commandResultOptional.get().replace("\\", "/");
 	}
 }
diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties
index 4db577044cc50bcb7b9e39f2bc33cebf5123dfcb..ed8465f8ad2b4078b9a9386c8bba70f8b7aaa2e4 100644
--- a/BudgetMasterServer/src/main/resources/languages/base_de.properties
+++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties
@@ -172,6 +172,7 @@ notification.settings.update.available=BudgetMaster Update "{0}" verfügbar
 notification.settings.database.delete.success=Datenbank erfolgreich gelöscht
 notification.settings.backup.run.success=Backup erfolgreich
 notification.settings.backup.run.error=Backup fehlgeschlagen
+notification.migration.error=Migration fehlgeschlagen: {0}
 upload.image.success=Erfolgreich hochgeladen
 upload.image.error=Fehler: {0}
 upload.image.error.no.file=Fehler: Keine Datei für Upload angegeben
diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties
index 763c8457395c6fb2c4a8b0d48335dea1d0e4b898..6e81d84b5d0e089dd6a4a8861141e2ed6c43ccb5 100644
--- a/BudgetMasterServer/src/main/resources/languages/base_en.properties
+++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties
@@ -173,6 +173,7 @@ notification.settings.update.available=BudgetMaster update "{0}" available
 notification.settings.database.delete.success=Successfully deleted database
 notification.settings.backup.run.success=Backup successful
 notification.settings.backup.run.error=Backup failed
+notification.migration.error=Migration failed: {0}
 upload.image.success=Upload successful
 upload.image.error=Error: {0}
 upload.image.error.no.file=Error: No file provided for upload
diff --git a/BudgetMasterServer/src/main/resources/templates/migration.ftl b/BudgetMasterServer/src/main/resources/templates/migration.ftl
index ea4cd54f4b608601f9dfe31c3ac985fc84acbd78..08aab2420e7ce6301be67589ddd7732b7bb31056 100644
--- a/BudgetMasterServer/src/main/resources/templates/migration.ftl
+++ b/BudgetMasterServer/src/main/resources/templates/migration.ftl
@@ -17,92 +17,95 @@
                         <div class="headline">${locale.getString("title.migration")}</div>
                     </div>
                 </div>
-                <div class="container">
-                    <form name="MigrationSettings" action="<@s.url '/migration'/>" method="post">
-                        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
-                        <div class="row">
-                            <div class="col s12 m12 l8 offset-l2">
-                                ${locale.getString("migration.settings.description")}
+                <@header.content>
+                    <div class="container">
+                        <form name="MigrationSettings" action="<@s.url '/migration'/>" method="post">
+                            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
+
+                            <div class="row">
+                                <div class="col s12 m12 l8 offset-l2">
+                                    ${locale.getString("migration.settings.description")}
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row notification-row">
-                            <div class="col s12 center-align">
-                                <div class="notification-wrapper">
-                                    <div class="notification background-yellow text-black">
-                                        <i class="fas fa-exclamation-triangle notification-item"></i>
-                                        <span class="notification-item left-align">${locale.getString("migration.settings.description.warning")}</span>
+                            <div class="row notification-row">
+                                <div class="col s12 center-align">
+                                    <div class="notification-wrapper">
+                                        <div class="notification background-yellow text-black">
+                                            <i class="fas fa-exclamation-triangle notification-item"></i>
+                                            <span class="notification-item left-align">${locale.getString("migration.settings.description.warning")}</span>
+                                        </div>
                                     </div>
                                 </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">public</i>
-                                <input id="migration-hostname" type="text" name="hostname" <@validation.validation "hostname"/> value="<#if migrationSettings.hostname()??>${migrationSettings.hostname()}</#if>" placeholder="localhost">
-                                <label for="migration-hostname">${locale.getString("migration.settings.hostname")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">public</i>
+                                    <input id="migration-hostname" type="text" name="hostname" <@validation.validation "hostname"/> value="<#if migrationSettings.hostname()??>${migrationSettings.hostname()}</#if>" placeholder="localhost">
+                                    <label for="migration-hostname">${locale.getString("migration.settings.hostname")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">dns</i>
-                                <input id="migration-port" type="number" min="1" max="65535" name="port" <@validation.validation "port"/> value="<#if migrationSettings.port()??>${migrationSettings.port()?c}</#if>" placeholder="5432">
-                                <label for="migration-port">${locale.getString("migration.settings.port")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">dns</i>
+                                    <input id="migration-port" type="number" min="1" max="65535" name="port" <@validation.validation "port"/> value="<#if migrationSettings.port()??>${migrationSettings.port()?c}</#if>" placeholder="5432">
+                                    <label for="migration-port">${locale.getString("migration.settings.port")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">inventory</i>
-                                <input id="migration-database-name" type="text" name="databaseName" <@validation.validation "databaseName"/> value="<#if migrationSettings.databaseName()??>${migrationSettings.databaseName()}</#if>">
-                                <label for="migration-database-name">${locale.getString("migration.settings.databaseName")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">inventory</i>
+                                    <input id="migration-database-name" type="text" name="databaseName" <@validation.validation "databaseName"/> value="<#if migrationSettings.databaseName()??>${migrationSettings.databaseName()}</#if>">
+                                    <label for="migration-database-name">${locale.getString("migration.settings.databaseName")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">person</i>
-                                <input id="migration-username" type="text" name="username" <@validation.validation "username"/> value="<#if migrationSettings.username()??>${migrationSettings.username()}</#if>">
-                                <label for="migration-username">${locale.getString("migration.settings.username")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">person</i>
+                                    <input id="migration-username" type="text" name="username" <@validation.validation "username"/> value="<#if migrationSettings.username()??>${migrationSettings.username()}</#if>">
+                                    <label for="migration-username">${locale.getString("migration.settings.username")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">vpn_key</i>
-                                <input id="migration-password" type="text" name="password" <@validation.validation "password"/> value="<#if migrationSettings.password()??>${migrationSettings.password()}</#if>">
-                                <label for="migration-password">${locale.getString("migration.settings.password")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">vpn_key</i>
+                                    <input id="migration-password" type="text" name="password" <@validation.validation "password"/> value="<#if migrationSettings.password()??>${migrationSettings.password()}</#if>">
+                                    <label for="migration-password">${locale.getString("migration.settings.password")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="col s12 m12 l8 offset-l2">
-                                ${locale.getString("migration.settings.verification.password.description")}
+                            <div class="row">
+                                <div class="col s12 m12 l8 offset-l2">
+                                    ${locale.getString("migration.settings.verification.password.description")}
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="input-field col s12 m12 l8 offset-l2">
-                                <i class="material-icons prefix">lock_open</i>
-                                <input id="migration-verification-password" type="password" name="verificationPassword" <@validation.validation "verificationPassword"/> value="">
-                                <label for="migration-verification-password">${locale.getString("migration.settings.verification.password")}</label>
+                            <div class="row">
+                                <div class="input-field col s12 m12 l8 offset-l2">
+                                    <i class="material-icons prefix">lock_open</i>
+                                    <input id="migration-verification-password" type="password" name="verificationPassword" <@validation.validation "verificationPassword"/> value="">
+                                    <label for="migration-verification-password">${locale.getString("migration.settings.verification.password")}</label>
+                                </div>
                             </div>
-                        </div>
 
-                        <div class="row">
-                            <div class="col m6 l4 offset-l2 right-align">
-                                <@header.buttonLink url='/' icon='clear' localizationKey='cancel' color='red'/>
-                            </div>
+                            <div class="row">
+                                <div class="col m6 l4 offset-l2 right-align">
+                                    <@header.buttonLink url='/' icon='clear' localizationKey='cancel' color='red'/>
+                                </div>
 
-                            <div class="col m6 l4 left-align">
-                                <@header.buttonSubmit name='action' icon='merge' localizationKey='info.button.migration.start' id='buttonMigrate' color='green'/>
+                                <div class="col m6 l4 left-align">
+                                    <@header.buttonSubmit name='action' icon='merge' localizationKey='info.button.migration.start' id='buttonMigrate' color='green'/>
+                                </div>
                             </div>
-                        </div>
-                    </form>
-                </div>
+                        </form>
+                    </div>
+                </@header.content>
             </div>
         </main>