diff --git a/sb4e.test/.classpath b/sb4e.test/.classpath
index ffa8aa244b3d25d7f04778db0c3b0215f7791d83..291fc90f3204c81b61541bf4fb2b7450984fb792 100644
--- a/sb4e.test/.classpath
+++ b/sb4e.test/.classpath
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9">
 		<attributes>
 			<attribute name="maven.pomderived" value="true"/>
 		</attributes>
 	</classpathentry>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="resources"/>
+	<classpathentry kind="src" path="src/"/>
+	<classpathentry kind="src" path="resources/"/>
 	<classpathentry kind="output" path="target/classes"/>
 </classpath>
diff --git a/sb4e.test/.settings/org.eclipse.jdt.core.prefs b/sb4e.test/.settings/org.eclipse.jdt.core.prefs
index 6e80039d3b822e65e46fbf18906ef652814e9505..63864f1a37b2edebce3e44a76bc790c00b5ba409 100644
--- a/sb4e.test/.settings/org.eclipse.jdt.core.prefs
+++ b/sb4e.test/.settings/org.eclipse.jdt.core.prefs
@@ -1,8 +1,8 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=9
+org.eclipse.jdt.core.compiler.compliance=9
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.source=9
diff --git a/sb4e.test/META-INF/MANIFEST.MF b/sb4e.test/META-INF/MANIFEST.MF
index ae208b70c2f3148cf17f073dd879ae07bd84dd60..0f3fb9a4f7322cd80d4dfd144942b4ac43f888fd 100644
--- a/sb4e.test/META-INF/MANIFEST.MF
+++ b/sb4e.test/META-INF/MANIFEST.MF
@@ -12,5 +12,7 @@ Require-Bundle: no.tobask.sb4e;bundle-version="0.9.0",
  org.eclipse.ui;bundle-version="3.109.0"
 Import-Package: org.eclipse.core.resources,
  org.eclipse.core.runtime;version="3.5.0",
+ org.eclipse.e4.core.contexts;version="1.6.0",
  org.eclipse.jdt.core,
+ org.eclipse.jdt.core.compiler,
  org.eclipse.swt.widgets
diff --git a/sb4e.test/resources/no/tobask/sb4e/test/Test.fxml b/sb4e.test/resources/no/tobask/sb4e/test/Test.fxml
index 5599e8b1dae787b952dc6fad07028b0956a47b03..2653627b42cc17744018d327480596ba54585797 100644
--- a/sb4e.test/resources/no/tobask/sb4e/test/Test.fxml
+++ b/sb4e.test/resources/no/tobask/sb4e/test/Test.fxml
@@ -2,7 +2,7 @@
 
 <?import javafx.scene.layout.AnchorPane?>
 
-<AnchorPane xmlns:fx="http://javafx.com/fxml/1">
+<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:id="mainWindow">
 	<!-- TODO Add Nodes -->
 </AnchorPane>
 
diff --git a/sb4e.test/src/no/tobask/sb4e/test/FxControllerValidatorTest.java b/sb4e.test/src/no/tobask/sb4e/test/FxControllerValidatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..344c6fa8daca93740139c9eca761e165ef1783b8
--- /dev/null
+++ b/sb4e.test/src/no/tobask/sb4e/test/FxControllerValidatorTest.java
@@ -0,0 +1,63 @@
+package no.tobask.sb4e.test;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.jdt.core.compiler.BuildContext;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.mockito.ArgumentCaptor;
+
+import javafx.embed.swt.FXCanvas;
+import no.tobask.sb4e.FxControllerProblem;
+import no.tobask.sb4e.FxControllerValidator;
+import no.tobask.sb4e.FxmlDocumentListener;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+import java.net.MalformedURLException;
+
+public class FxControllerValidatorTest {
+	
+	IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject("testProject");
+	
+	@Rule
+	public final ExternalResource projectResource = new JavaProjectResource(project);
+	
+	@Test
+	public void buildStarting_discoversAndReportsProblems_whenThereAreProblems() throws MalformedURLException {
+		FxControllerValidator validator = new FxControllerValidator();
+		FxmlDocumentListener documentListener = mock(FxmlDocumentListener.class);
+		validator.setDocumentListener(documentListener);
+		
+		BuildContext buildContext = mock(BuildContext.class);
+		IFile controller = project.getFolder("src").getFile("TestController.java");
+		IFile document = project.getFolder("src").getFile("Test.fxml");
+		
+		when(buildContext.getFile()).thenReturn(controller);
+		when(documentListener.isAssignedController("src.TestController")).thenReturn(true);
+		when(documentListener.getDocument("src.TestController"))
+			.thenReturn(document.getLocationURI().toURL());
+
+		validator.buildStarting(new BuildContext[] {buildContext}, true);
+		ArgumentCaptor<CategorizedProblem[]> problems = ArgumentCaptor
+				.forClass(CategorizedProblem[].class);
+		verify(buildContext).recordNewProblems(problems.capture());
+		CategorizedProblem[] recordedProblems = problems.getValue();
+		assertEquals(1, recordedProblems.length);
+		FxControllerProblem problem = (FxControllerProblem) recordedProblems[0];
+		String[] arguments = problem.getArguments();
+		assertEquals(1, arguments.length);
+		assertEquals("mainWindow;javafx.scene.layout.AnchorPane", arguments[0]);
+	}
+
+}
diff --git a/sb4e.test/src/no/tobask/sb4e/test/JavaProjectResource.java b/sb4e.test/src/no/tobask/sb4e/test/JavaProjectResource.java
index 039f8f7ae620b85f91318f5c91379bb8513646e9..52c2fc85838c20e3342b69ad403d40c1b00bbece 100644
--- a/sb4e.test/src/no/tobask/sb4e/test/JavaProjectResource.java
+++ b/sb4e.test/src/no/tobask/sb4e/test/JavaProjectResource.java
@@ -48,11 +48,12 @@ public class JavaProjectResource extends ExternalResource {
 	
 	private String getContents(String fileName) throws IOException {
 		StringBuilder builder = new StringBuilder();
-		InputStream stream = getClass().getResourceAsStream(fileName);
-		BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
-		String line;
-		while ((line = reader.readLine()) != null) {
-			builder.append(line);
+		try (InputStream stream = getClass().getResourceAsStream(fileName)) {
+			BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+			String line;
+			while ((line = reader.readLine()) != null) {
+				builder.append(line);
+			}
 		}
 		return builder.toString();
 	}
diff --git a/sb4e/META-INF/MANIFEST.MF b/sb4e/META-INF/MANIFEST.MF
index 139fa17ee2f38b4ecf0b5071b8c2a6963bab3033..a177ad00f67480fb7684e12fa93f013722f2d3d7 100644
--- a/sb4e/META-INF/MANIFEST.MF
+++ b/sb4e/META-INF/MANIFEST.MF
@@ -27,8 +27,13 @@ Import-Package: com.oracle.javafx.scenebuilder.kit.editor,
  org.eclipse.e4.core.di.annotations;version="1.6.0",
  org.eclipse.e4.ui.di,
  org.eclipse.jdt.core,
+ org.eclipse.jdt.core.compiler,
  org.eclipse.jdt.core.dom,
- org.eclipse.jdt.launching
+ org.eclipse.jdt.core.dom.rewrite,
+ org.eclipse.jdt.launching,
+ org.eclipse.jdt.ui.text.java,
+ org.eclipse.jdt.ui.text.java.correction,
+ org.eclipse.text.edits
 Bundle-ActivationPolicy: lazy
 Bundle-ClassPath: .
 Export-Package: no.tobask.sb4e,
diff --git a/sb4e/plugin.xml b/sb4e/plugin.xml
index 0ab2b4c17323c2e7559b6a718368f92bb98af3aa..7298087ddeb36d8d67c96e1f49773a2f4e405633 100644
--- a/sb4e/plugin.xml
+++ b/sb4e/plugin.xml
@@ -80,5 +80,44 @@
             class="no.tobask.sb4e.handlers.IncludeFxmlHandler">
       </handler>
    </extension>
+   <extension
+         point="org.eclipse.jdt.core.compilationParticipant">
+      <compilationParticipant
+            class="no.tobask.sb4e.FxControllerValidator"
+            id="FxControllerValidator"
+            createsProblems="true">
+            <managedMarker
+                  markerType="no.tobask.sb4e.fxcontrollerproblemmarker">
+            </managedMarker>
+      </compilationParticipant>
+   </extension>
+   <extension
+         id="no.tobask.sb4e.fxcontrollerproblemmarker"
+         name="Fx controller problem"
+         point="org.eclipse.core.resources.markers">
+      <super
+            type="org.eclipse.jdt.core.problem">
+      </super>
+   </extension>
+   <extension
+         point="org.eclipse.jdt.ui.quickFixProcessors">
+      <quickFixProcessor
+            class="no.tobask.sb4e.MissingFxIdsFixer"
+            id="no.tobask.sb4e.missingfxidsfixer"
+            name="Missing fxids fixer">
+         <handledMarkerTypes>
+            <markerType
+                  id="no.tobask.sb4e.fxcontrollerproblemmarker">
+            </markerType>
+         </handledMarkerTypes>
+         <enablement>
+            <with variable="projectNatures">
+                  <iterate operator="or">
+                  		<equals value="org.eclipse.jdt.core.javanature"/>
+                  </iterate>
+            </with>
+         </enablement>
+      </quickFixProcessor>
+   </extension>
 
 </plugin>
diff --git a/sb4e/src/no/tobask/sb4e/Activator.java b/sb4e/src/no/tobask/sb4e/Activator.java
index 272fa110a5908b5b9241b5c7f70bffa06d34ae75..050eb555e892bae965c6016a84da1a2342e67a56 100644
--- a/sb4e/src/no/tobask/sb4e/Activator.java
+++ b/sb4e/src/no/tobask/sb4e/Activator.java
@@ -1,15 +1,28 @@
 package no.tobask.sb4e;
 
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
 public class Activator implements BundleActivator {
 
 	private static BundleContext context;
+	private static FxmlDocumentListener fxmlDocumentListener =
+			new FxmlDocumentListener();
+	private static EclipseProjectsClassLoader classLoader = new EclipseProjectsClassLoader();
 
 	public static BundleContext getContext() {
 		return context;
 	}
+	
+	public static FxmlDocumentListener getFxmlDocumentListener() {
+		return fxmlDocumentListener;
+	}
+	
+	public static EclipseProjectsClassLoader getClassLoader() {
+		return classLoader;
+	}
 
 	/*
 	 * (non-Javadoc)
@@ -17,6 +30,8 @@ public class Activator implements BundleActivator {
 	 */
 	public void start(BundleContext bundleContext) throws Exception {
 		Activator.context = bundleContext;
+		ResourcesPlugin.getWorkspace().addResourceChangeListener(fxmlDocumentListener,
+				IResourceChangeEvent.POST_CHANGE);
 	}
 
 	/*
@@ -25,6 +40,7 @@ public class Activator implements BundleActivator {
 	 */
 	public void stop(BundleContext bundleContext) throws Exception {
 		Activator.context = null;
+		ResourcesPlugin.getWorkspace().removeResourceChangeListener(fxmlDocumentListener);
 	}
 
 }
diff --git a/sb4e/src/no/tobask/sb4e/FxControllerProblem.java b/sb4e/src/no/tobask/sb4e/FxControllerProblem.java
new file mode 100644
index 0000000000000000000000000000000000000000..3023148f1a547de02fafca9e330958fa4fff29fb
--- /dev/null
+++ b/sb4e/src/no/tobask/sb4e/FxControllerProblem.java
@@ -0,0 +1,91 @@
+package no.tobask.sb4e;
+
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+
+public class FxControllerProblem extends CategorizedProblem {
+	
+	List<String> missingIds;
+	IResource resource;
+
+	public FxControllerProblem(List<String> missingIds, IResource resource) {
+		this.missingIds = missingIds;
+		this.resource = resource;
+	}
+
+	@Override
+	public String[] getArguments() {
+		return missingIds.toArray(new String[0]);
+	}
+
+	@Override
+	public int getID() {
+		return 1234;
+	}
+
+	@Override
+	public String getMessage() {
+		return "One or more fx:ids from associated FXML document do not have corresponding fields";
+	}
+
+	@Override
+	public char[] getOriginatingFileName() {
+		return resource.getName().toCharArray();
+	}
+
+	@Override
+	public int getSourceEnd() {
+		return -1;
+	}
+
+	@Override
+	public int getSourceLineNumber() {
+		return 1;
+	}
+
+	@Override
+	public int getSourceStart() {
+		return 0;
+	}
+
+	@Override
+	public boolean isError() {
+		return false;
+	}
+
+	@Override
+	public boolean isWarning() {
+		return true;
+	}
+
+	@Override
+	public void setSourceEnd(int sourceEnd) {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public void setSourceLineNumber(int lineNumber) {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public void setSourceStart(int sourceStart) {
+		// TODO Auto-generated method stub
+
+	}
+
+	@Override
+	public int getCategoryID() {
+		return CAT_POTENTIAL_PROGRAMMING_PROBLEM;
+	}
+
+	@Override
+	public String getMarkerType() {
+		return "no.tobask.sb4e.fxcontrollerproblemmarker";
+	}
+
+}
diff --git a/sb4e/src/no/tobask/sb4e/FxControllerValidator.java b/sb4e/src/no/tobask/sb4e/FxControllerValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad81556e3d3aa98f392c8c52ebf2864c15e5668f
--- /dev/null
+++ b/sb4e/src/no/tobask/sb4e/FxControllerValidator.java
@@ -0,0 +1,101 @@
+package no.tobask.sb4e;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.compiler.BuildContext;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.CompilationParticipant;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.ui.PlatformUI;
+
+import com.oracle.javafx.scenebuilder.kit.i18n.I18N;
+import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument;
+import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance;
+import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
+
+public class FxControllerValidator extends CompilationParticipant {
+					
+	FxmlDocumentListener documentListener;
+	
+	public FxControllerValidator() {
+		documentListener = Activator.getFxmlDocumentListener();
+	}
+	
+	public void setDocumentListener(FxmlDocumentListener documentListener) {
+		this.documentListener = documentListener;
+	}
+	
+	@Override
+	public void buildStarting(BuildContext[] files, boolean isBatch) {
+		for (BuildContext file : files) {
+			ICompilationUnit clazz = (ICompilationUnit) JavaCore.create(file.getFile());
+			String className = clazz.findPrimaryType().getFullyQualifiedName();
+			if (documentListener.isAssignedController(className)) {
+				URL documentLocation = documentListener.getDocument(className);
+				try {
+					String fxmlContent = FXOMDocument.readContentFromURL(documentLocation);
+					FXOMDocument document = new FXOMDocument(fxmlContent, documentLocation,
+							Activator.getClassLoader(), I18N.getBundle());
+					CompilationUnit ast = getAst(clazz);
+					file.recordNewProblems(getProblems(ast, document));
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+	
+	private CompilationUnit getAst(ICompilationUnit source) {
+		ASTParser parser = ASTParser.newParser(AST.JLS10);
+		parser.setSource(source);
+		parser.setResolveBindings(true);
+		return (CompilationUnit) parser.createAST(null);
+	}
+
+	@Override
+	public boolean isActive(IJavaProject project) {
+		return true;
+	}
+	
+	private CategorizedProblem[] getProblems(CompilationUnit ast, FXOMDocument document) {
+		FxControllerVisitor visitor = new FxControllerVisitor();
+		ast.accept(visitor);
+		Map<String, String> controllerIds = visitor.getFxIds();
+		Map<String, FXOMObject> documentIds = document.collectFxIds();
+		List<String> missingIds = new ArrayList<>();
+		for (Entry<String, FXOMObject> id : documentIds.entrySet()) {
+			if (!controllerIds.containsKey(id.getKey())) {
+				FXOMObject fxomObject = id.getValue();
+				if (fxomObject instanceof FXOMInstance) {
+					FXOMInstance instance = (FXOMInstance) fxomObject;
+					missingIds.add(id.getKey() + ";" + instance.getDeclaredClass().getName());
+				}
+			}
+		}
+
+		if (!missingIds.isEmpty()) {
+			try {
+				IResource resource = ast.getTypeRoot().getCorrespondingResource();
+				return new CategorizedProblem[] {new FxControllerProblem(missingIds, resource)};
+			} catch (JavaModelException e) {
+				e.printStackTrace();
+			}
+		}
+		return new CategorizedProblem[0];
+	}
+	
+}
diff --git a/sb4e/src/no/tobask/sb4e/FxControllerVisitor.java b/sb4e/src/no/tobask/sb4e/FxControllerVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..468295d23cdda04f96aab38b3367d06d1daad7ae
--- /dev/null
+++ b/sb4e/src/no/tobask/sb4e/FxControllerVisitor.java
@@ -0,0 +1,66 @@
+package no.tobask.sb4e;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.Annotation;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.IExtendedModifier;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.Modifier;
+import org.eclipse.jdt.core.dom.SimpleType;
+import org.eclipse.jdt.core.dom.Type;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+
+import javafx.fxml.FXML;
+
+public class FxControllerVisitor extends ASTVisitor {
+	
+	private Map<String, String> fxIds = new HashMap<>(); // var name -> type
+	private static final String FXML_ANNOTATION = FXML.class.getSimpleName();
+	
+	@Override
+	public boolean visit(FieldDeclaration node) {
+		if (isFxIdCandidate(node)) {
+			Type type = node.getType();
+			ITypeBinding typeBinding = type.resolveBinding();
+			String typeName;
+			if (typeBinding != null) {
+				typeName = typeBinding.getQualifiedName();
+			} else {
+				if (type.isSimpleType()) {
+					typeName = ((SimpleType) type).getName().getFullyQualifiedName();
+				} else {
+					typeName = "UNKNOWN";
+				}
+			}
+			List<VariableDeclarationFragment> fragments = node.fragments();
+			String variableName = fragments.get(0).getName().toString();
+			fxIds.put(variableName, typeName);
+		}
+		return super.visit(node);
+	}
+	
+	
+	public Map<String, String> getFxIds() {
+		return fxIds;
+	}
+
+	private boolean isFxIdCandidate(FieldDeclaration field)	{
+		List<IExtendedModifier> modifiers = field.modifiers();
+		if (Modifier.isPublic(field.getModifiers())) {
+			return true;
+		}
+		boolean hasFxmlAnnotation = false;
+		for (IExtendedModifier modifier : modifiers) {
+			if (modifier.isAnnotation()) {
+				String annotationName = ((Annotation) modifier).getTypeName().toString();
+				hasFxmlAnnotation = hasFxmlAnnotation || annotationName.equals(FXML_ANNOTATION);
+			}
+		}
+		return hasFxmlAnnotation;
+	}
+
+}
diff --git a/sb4e/src/no/tobask/sb4e/FxmlDocumentListener.java b/sb4e/src/no/tobask/sb4e/FxmlDocumentListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..f86996d60f2331aaf2165bf12db81dcbba02d410
--- /dev/null
+++ b/sb4e/src/no/tobask/sb4e/FxmlDocumentListener.java
@@ -0,0 +1,158 @@
+package no.tobask.sb4e;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+public class FxmlDocumentListener implements IResourceChangeListener {
+
+	private Map<String, URL> controllers = new HashMap<>();
+	private boolean discoveryPerformed = false;
+	
+	@Override
+	public void resourceChanged(IResourceChangeEvent event) {
+		try {
+			event.getDelta().accept(this::visit);
+		} catch (CoreException e) {
+			e.printStackTrace();
+		}
+	}
+	
+	private boolean visit(IResourceDelta delta) {
+		IResource resource = delta.getResource();
+		if (resource != null && resource.getType() == IResource.FILE) {
+			IFile file = (IFile) resource;
+			String extension = file.getFileExtension();
+			if (extension != null && extension.equals("fxml")) {
+				try {
+					updateControllers(file);
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	private void updateControllers(IFile fxmlFile) throws IOException {
+		String controller = getController(fxmlFile);
+		if (controller != null) {
+			if (fxmlFile.exists()) {
+				controllers.put(controller, fxmlFile.getLocationURI().toURL());
+			} else {
+				controllers.remove(controller);
+			}
+		}
+	}
+
+	private String getController(IFile fxmlFile) throws IOException {
+		try (InputStream inputStream = fxmlFile.getLocationURI().toURL().openStream()) {
+			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+			String line;
+			while ((line = reader.readLine()) != null) {
+				String[] tokens = line.split(" ");
+				for (String token : tokens) {
+					if (token.startsWith("fx:controller=")) {
+						return token.split("=")[1].replaceAll("[^a-zA-Z0-9\\.]", "");
+					}
+				}
+			}
+		}
+		return null;
+	}
+	
+	public boolean isAssignedController(String controllerName) {
+		if (!discoveryPerformed) {
+			discoverControllers();
+			discoveryPerformed = true;
+		}
+		return controllers.containsKey(controllerName);
+	}
+
+	public URL getDocument(String controllerName) {
+		if (!discoveryPerformed) {
+			discoverControllers();
+			discoveryPerformed = true;
+		}
+		return controllers.get(controllerName);
+	}
+	
+	private void discoverControllers() {
+		try {
+			for (IFile fxmlFile : getAllFxmlFilesInWorkspace()) {
+				updateControllers(fxmlFile);
+			}
+		} catch (JavaModelException | IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private Collection<IFile> getAllFxmlFilesInWorkspace() throws JavaModelException {
+		Collection<IFile> files = new ArrayList<>();
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		for (IProject project : root.getProjects()) {
+			IJavaProject javaProject = JavaCore.create(project);
+			files.addAll(getFxmlFiles(javaProject));
+		}
+		return files;
+	}
+
+	private Collection<IFile> getFxmlFiles(IJavaProject javaProject) throws JavaModelException {
+		Collection<IFile> files = new ArrayList<>();
+		for (IPackageFragmentRoot pkgRoot : javaProject.getPackageFragmentRoots()) {
+			if (pkgRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+				files.addAll(getFxmlFiles(pkgRoot));
+			}
+		}
+		return files;
+	}
+
+	private List<IFile> getFxmlFiles(IPackageFragmentRoot pkgRoot) throws JavaModelException {
+		List<IFile> files = new ArrayList<>();
+		for (IJavaElement child : pkgRoot.getChildren()) {
+			if (child.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
+				files.addAll(getFxmlFiles((IPackageFragment) child));
+			}
+		}
+		return files;
+	}
+	
+	private List<IFile> getFxmlFiles(IPackageFragment pkg) throws JavaModelException {
+		List<IFile> files = new ArrayList<>();
+		for (Object resource : pkg.getNonJavaResources()) {
+			if (resource instanceof IFile) {
+				IFile file = (IFile) resource;
+				String extension = file.getFileExtension();
+				if (extension != null && extension.equals("fxml")) {
+					files.add(file);
+				}
+			}
+		}
+		return files;
+	}
+
+}
diff --git a/sb4e/src/no/tobask/sb4e/MissingFxIdsFixer.java b/sb4e/src/no/tobask/sb4e/MissingFxIdsFixer.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a1dacdbc5568426e827bafb3c1e2ea3f48fe1d4
--- /dev/null
+++ b/sb4e/src/no/tobask/sb4e/MissingFxIdsFixer.java
@@ -0,0 +1,97 @@
+package no.tobask.sb4e;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.BodyDeclaration;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.MarkerAnnotation;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+import org.eclipse.jdt.ui.text.java.IInvocationContext;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+import org.eclipse.jdt.ui.text.java.IQuickFixProcessor;
+import org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal;
+
+public class MissingFxIdsFixer implements IQuickFixProcessor {
+
+	@Override
+	public boolean hasCorrections(ICompilationUnit unit, int problemId) {
+		return problemId == 1234;
+	}
+
+	@Override
+	public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations)
+			throws CoreException {
+		CompilationUnit unit = context.getASTRoot();
+		AST unitAst = unit.getAST();
+		TypeDeclaration type = (TypeDeclaration) unit.types().get(0);
+
+		ASTRewrite rewrite = ASTRewrite.create(unitAst);
+		ListRewrite bodyDeclarationsRewrite = rewrite.getListRewrite(type,
+				TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
+		ListRewrite importsRewrite = rewrite.getListRewrite(unit, CompilationUnit.IMPORTS_PROPERTY);
+
+		List<ImportDeclaration> imports = unit.imports();
+		List<String> importNames = imports.stream().map(i -> i.getName().getFullyQualifiedName())
+				.collect(Collectors.toList());
+		
+		String[] missingIds = locations[0].getProblemArguments();
+		for (String missingId : missingIds) {
+			String[] parts = missingId.split(";");
+			String variableName = parts[0];
+			String variableType = parts[1];
+			String[] variableTypeParts = variableType.split("\\.");
+			
+			VariableDeclarationFragment variableFragment = unitAst.newVariableDeclarationFragment();
+			variableFragment.setName(unitAst.newSimpleName(variableName));
+			
+			FieldDeclaration field = unitAst.newFieldDeclaration(variableFragment);
+			String simpleTypeName = variableTypeParts[variableTypeParts.length-1];
+			field.setType(unitAst.newSimpleType(unitAst.newName(simpleTypeName)));
+			
+			AST fieldAst = field.getAST();
+			MarkerAnnotation annotation = fieldAst.newMarkerAnnotation();
+			annotation.setTypeName(unitAst.newSimpleName("FXML"));
+			field.modifiers().add(annotation);
+			
+			ASTNode lastField = getLastField(type);
+			if (lastField == null) {
+				bodyDeclarationsRewrite.insertFirst(field, null);
+			} else {
+				bodyDeclarationsRewrite.insertAfter(field, lastField, null);				
+			}
+			
+			if (!importNames.contains(variableType)) {
+				ImportDeclaration importDeclaration = unitAst.newImportDeclaration();
+				importDeclaration.setName(unitAst.newName(variableType));
+				importsRewrite.insertLast(importDeclaration, null);
+			}
+		}
+		
+		ASTRewriteCorrectionProposal fix = new ASTRewriteCorrectionProposal("Add missing fields",
+				context.getCompilationUnit(), rewrite, 1);
+		return new IJavaCompletionProposal[] {fix};
+	}
+	
+	private ASTNode getLastField(TypeDeclaration type) {
+		List<BodyDeclaration> bodyDeclarations = type.bodyDeclarations();
+		ASTNode lastField = null;
+		for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
+			if (bodyDeclaration.getNodeType() == ASTNode.FIELD_DECLARATION) {
+				lastField = bodyDeclaration;
+			}
+		}
+		return lastField;
+	}
+
+}