diff --git a/ChangeLog b/ChangeLog
index c8d62413882aafc5e248b2dcbca11bc32e8af904..9b5654bc25c1e8c136ee28fd3f8150d75d78c57e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,9 @@
     * Added fluent interface for initializing the components of an AST node
     (component setting methods return the AST node itself).
     See https://bitbucket.org/jastadd/jastadd2/pull-requests/10
+    * Added option --optimize-imports which can be enabled to
+    reduce the number of unused imports in generated code.
+    See https://bitbucket.org/jastadd/jastadd2/issues/310/optimize-imports
 
 2019-03-18  Jesper Öqvist <jesper.oqvist@cs.lth.se>
 
diff --git a/src/jastadd/ast/JastAddCodeGen.jadd b/src/jastadd/ast/JastAddCodeGen.jadd
index 961deabc887fa10e28af9a0bfe314ee8d41cc3ab..b2c19256c389a2167e12e30c4555ea5871b12031 100644
--- a/src/jastadd/ast/JastAddCodeGen.jadd
+++ b/src/jastadd/ast/JastAddCodeGen.jadd
@@ -29,6 +29,7 @@
 import org.jastadd.ast.AST.*;
 
 import java.io.*;
+import java.nio.charset.StandardCharsets;
 
 import java.util.*;
 
@@ -228,17 +229,8 @@ aspect JastAddCodeGen {
   public void InterfaceDecl.jastAddGen(boolean publicModifier) {
     File file = grammar().targetJavaFile(name());
     try {
-      PrintStream stream = new PrintStream(new FileOutputStream(file));
-      if ( !config().license.isEmpty()) {
-        stream.println(config().license);
-      }
-
-      // TODO(joqvist): move to template.
-      if (!config().packageName().isEmpty()) {
-        stream.println("package " + config().packageName() + ";\n");
-      }
-
-      stream.print(grammar().genImportsList());
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      PrintStream stream = new PrintStream(bos, true, "UTF-8");
       stream.println(docComment());
       stream.println(typeDeclarationString());
 
@@ -248,7 +240,22 @@ aspect JastAddCodeGen {
 
       stream.println("}");
       stream.close();
-    } catch (FileNotFoundException f) {
+      String code = new String(bos.toByteArray(), StandardCharsets.UTF_8);
+
+      PrintStream fstream = new PrintStream(new FileOutputStream(file));
+      if ( !config().license.isEmpty()) {
+        fstream.println(config().license);
+      }
+
+      // TODO(joqvist): move to template.
+      if (!config().packageName().isEmpty()) {
+        fstream.println("package " + config().packageName() + ";\n");
+      }
+
+      fstream.println(grammar().genImportsList(code));
+      fstream.print(code);
+      fstream.close();
+    } catch (IOException e) {
       System.err.println("Could not create file " + file.getName() + " in " + file.getParent());
       System.exit(1);
     }
@@ -259,24 +266,28 @@ aspect JastAddCodeGen {
   public void EnumDecl.jastAddGen(boolean publicModifier) {
     File file = grammar().targetJavaFile(name());
     try {
-      PrintStream stream = new PrintStream(new FileOutputStream(file));
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      PrintStream stream = new PrintStream(bos, true, "UTF-8");
+      stream.println(Unparser.unparse(simpleNode));
+      stream.close();
+      String code = new String(bos.toByteArray(), StandardCharsets.UTF_8);
+
+      PrintStream fstream = new PrintStream(new FileOutputStream(file));
       if (!config().license.isEmpty()) {
-        stream.println(config().license);
+        fstream.println(config().license);
       }
 
       // TODO(joqvist): move to template.
       if (!config().packageName().isEmpty()) {
-        stream.println("package " + config().packageName() + ";\n");
+        fstream.println("package " + config().packageName() + ";\n");
       }
 
-      stream.print(grammar().genImportsList());
-      stream.println(docComment());
-
-      stream.println(Unparser.unparse(simpleNode));
-
-      stream.close();
-    } catch (FileNotFoundException f) {
-      System.err.println("Could not create file " + file.getName() + " in " + file.getParent());
+      fstream.print(grammar().genImportsList(code));
+      fstream.println(docComment());
+      fstream.println(code);
+      fstream.close();
+    } catch (IOException f) {
+      System.err.format("Could not create file %s in %s.%n", file.getName(), file.getParent());
       System.exit(1);
     }
   }
@@ -284,18 +295,8 @@ aspect JastAddCodeGen {
   public void ClassDecl.jastAddGen(boolean publicModifier) {
     File file = grammar().targetJavaFile(name());
     try {
-      PrintStream stream = new PrintStream(new FileOutputStream(file));
-      if ( !config().license.isEmpty()) {
-        stream.println(config().license);
-      }
-
-      // TODO(joqvist): move to template.
-      if (!config().packageName().isEmpty()) {
-        stream.println("package " + config().packageName() + ";\n");
-      }
-
-      stream.print(grammar().genImportsList());
-      stream.println(docComment());
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      PrintStream stream = new PrintStream(bos, true, "UTF-8");
       stream.println(typeDeclarationString());
       StringBuffer buf = new StringBuffer();
       for (ClassBodyObject obj : classBodyDecls) {
@@ -306,13 +307,27 @@ aspect JastAddCodeGen {
         buf.append("\n\n");
       }
       stream.println(buf.toString());
-
       emitAbstractSyns(stream);
       emitInhDeclarations(stream);
-
-      stream.println("}");
       stream.close();
-    } catch (FileNotFoundException f) {
+      String code = new String(bos.toByteArray(), StandardCharsets.UTF_8);
+
+      PrintStream fstream = new PrintStream(new FileOutputStream(file));
+      if ( !config().license.isEmpty()) {
+        fstream.println(config().license);
+      }
+
+      // TODO(joqvist): move to template.
+      if (!config().packageName().isEmpty()) {
+        fstream.println("package " + config().packageName() + ";\n");
+      }
+
+      fstream.print(grammar().genImportsList(code));
+      fstream.println(docComment());
+      fstream.println(code);
+      fstream.println("}");
+      fstream.close();
+    } catch (IOException f) {
       System.err.format("Could not create file %s in %s.%n", file.getName(), file.getParent());
       System.exit(1);
     }
@@ -321,24 +336,9 @@ aspect JastAddCodeGen {
   public void ASTDecl.jastAddGen(boolean publicModifier) {
     File file = grammar().targetJavaFile(name());
     try {
-      PrintStream stream = new PrintStream(new FileOutputStream(file));
-
-      // Insert comment notifying that this is a generated file.
-      stream.format("/* This file was generated with %s */%n",
-          JastAdd.getLongVersionString());
-
-      if (!config().license.isEmpty()) {
-        stream.println(config().license);
-      }
-
-      // TODO(joqvist): move to template.
-      if (!config().packageName().isEmpty()) {
-        stream.format("package %s;%n", config().packageName());
-      }
-
-      stream.print(grammar().genImportsList());
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      PrintStream stream = new PrintStream(bos, true, "UTF-8");
 
-      stream.println(docComment());
       stream.print("public ");
       if (hasAbstract()) {
         stream.print("abstract ");
@@ -363,11 +363,30 @@ aspect JastAddCodeGen {
       }
       stream.print(jastAddImplementsList());
       stream.println(" {");
-
       jastAddAttributes(stream);
-      stream.println("}");
       stream.close();
-    } catch (FileNotFoundException f) {
+      String code = new String(bos.toByteArray(), StandardCharsets.UTF_8);
+
+      PrintStream fstream = new PrintStream(new FileOutputStream(file));
+
+      // Insert comment notifying that this is a generated file.
+      fstream.format("/* This file was generated with %s */%n",
+          JastAdd.getLongVersionString());
+
+      if (!config().license.isEmpty()) {
+        fstream.println(config().license);
+      }
+
+      // TODO(joqvist): move to template.
+      if (!config().packageName().isEmpty()) {
+        fstream.format("package %s;%n", config().packageName());
+      }
+      fstream.print(grammar().genImportsList(code));
+      fstream.println(docComment());
+      fstream.println(code);
+      fstream.println("}");
+      fstream.close();
+    } catch (IOException f) {
       System.err.format("Could not create file %s in %s%n", file.getName(), file.getParent());
       System.exit(1);
     }
diff --git a/src/jastadd/ast/JragCodeGen.jrag b/src/jastadd/ast/JragCodeGen.jrag
index fba10787d9277f07220ac276930ac3916a485871..6478b3dc1a0b4b91a15ee3b57cf5ce98aeb6b710 100644
--- a/src/jastadd/ast/JragCodeGen.jrag
+++ b/src/jastadd/ast/JragCodeGen.jrag
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2016, The JastAdd Team
+/* Copyright (c) 2005-2019, The JastAdd Team
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -118,31 +118,133 @@ aspect JragCodeGen {
     throw new Error("unhandled cache mode");
   }
 
+  public class ImportDecl {
+    public final String modifier;
+    public final String name;
 
-  public String Grammar.genImportsList() {
-    Set imports = new LinkedHashSet();
+    public ImportDecl(String modifier, String name) {
+      this.modifier = modifier;
+      this.name = name;
+    }
+
+    public ImportDecl(String name) {
+      this("", name);
+    }
+
+    @Override public int hashCode() {
+      return getClass().hashCode() ^ modifier.hashCode() ^ name.hashCode();
+    }
+
+    @Override public boolean equals(Object other) {
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      ImportDecl o = (ImportDecl) other;
+      return o.name.equals(name) && o.modifier.equals(modifier);
+    }
+
+    @Override public String toString() {
+      return String.format("import%s %s;", modifier, name);
+    }
+  }
+
+  public class MultiImportDecl extends ImportDecl {
+    public MultiImportDecl(String modifier, String name) {
+      super(modifier, name);
+    }
+
+    public MultiImportDecl(String name) {
+      this("", name);
+    }
+  }
+
+  class ImportSet {
+    public final Map<String, Node> all = new HashMap<String, Node>();
+    public final Map<String, Node> unmatched = new HashMap<String, Node>();
+
+    static class Node {
+        public final Collection<ImportDecl> decls = new ArrayList<ImportDecl>();
+        public boolean matched = false;
+    }
+
+    public ImportSet(Collection<ImportDecl> imports) {
+      for (ImportDecl decl : imports) {
+        if (decl instanceof MultiImportDecl) {
+          continue;
+        }
+        int i = decl.name.lastIndexOf('.');
+        String name = decl.name.substring(i+1);
+        Node node = all.get(name);
+        if (node == null) {
+          node = new Node();
+          all.put(name, node);
+          unmatched.put(name, node);
+        }
+        node.decls.add(decl);
+      }
+    }
+  }
+
+  public Set<ImportDecl> Grammar.importDecls() {
+    Set<ImportDecl> imports = new LinkedHashSet<ImportDecl>();
     if (config().concurrentEval()) {
       // Extra import declarations needed for concurrent code generation.
-      imports.add("import java.util.concurrent.atomic.AtomicInteger;");
-      imports.add("import java.util.concurrent.atomic.AtomicReference;");
-      imports.add("import java.util.concurrent.Future;");
-      imports.add("import java.util.concurrent.Executors;");
-      imports.add("import java.util.concurrent.ExecutorService;");
-      imports.add("import java.util.concurrent.ExecutionException;");
-      imports.add("import java.util.concurrent.Callable;");
-      imports.add("import java.util.concurrent.ConcurrentMap;");
-      imports.add("import java.util.concurrent.ConcurrentHashMap;");
-      imports.add("import java.util.ArrayList;");
-      imports.add("import java.util.LinkedList;");
-      imports.add("import java.util.Collection;");
+      imports.add(new ImportDecl("java.util.concurrent.atomic.AtomicInteger"));
+      imports.add(new ImportDecl("java.util.concurrent.atomic.AtomicReference"));
+      imports.add(new ImportDecl("java.util.concurrent.Future"));
+      imports.add(new ImportDecl("java.util.concurrent.Executors"));
+      imports.add(new ImportDecl("java.util.concurrent.ExecutorService"));
+      imports.add(new ImportDecl("java.util.concurrent.ExecutionException"));
+      imports.add(new ImportDecl("java.util.concurrent.Callable"));
+      imports.add(new ImportDecl("java.util.concurrent.ConcurrentMap"));
+      imports.add(new ImportDecl("java.util.concurrent.ConcurrentHashMap"));
+      imports.add(new ImportDecl("java.util.ArrayList"));
+      imports.add(new ImportDecl("java.util.LinkedList"));
+      imports.add(new ImportDecl("java.util.Collection"));
     }
     for (org.jastadd.jrag.AST.ASTCompilationUnit u : compilationUnits) {
       imports.addAll(Unparser.getImports(u));
     }
+    return imports;
+  }
+
+  public String Grammar.genImportsList(String code) {
+    Set<ImportDecl> imports = importDecls();
+    ImportSet set = new ImportSet(imports);
+    boolean optimize = config().optimizeImports.value();
+    if (optimize) {
+      for (int i = 0; i < code.length(); ++i) {
+        if (Character.isJavaIdentifierStart(code.charAt(i))) {
+          int end = i+1;
+          while (end < code.length() && Character.isJavaIdentifierPart(code.charAt(end))) {
+            end += 1;
+          }
+          String id = code.substring(i, end);
+          if (set.unmatched.containsKey(id)) {
+            set.unmatched.get(id).matched = true;
+            set.unmatched.remove(id);
+            if (set.unmatched.isEmpty()) {
+              break;
+            }
+          }
+          i = end-1;
+        }
+      }
+    }
     StringBuilder buf = new StringBuilder();
-    for (Iterator iter = imports.iterator(); iter.hasNext(); ) {
-      buf.append(iter.next());
-      buf.append('\n');
+    for (ImportDecl decl : imports) {
+      if (decl instanceof MultiImportDecl) {
+        buf.append(decl);
+        buf.append('\n');
+      }
+    }
+    for (ImportSet.Node node : set.all.values()) {
+      if (!optimize || node.matched) {
+        for (ImportDecl decl : node.decls) {
+          buf.append(decl);
+          buf.append('\n');
+        }
+      }
     }
     return buf.toString();
   }
diff --git a/src/java/org/jastadd/Configuration.java b/src/java/org/jastadd/Configuration.java
index bd77c0421ac3f1cbad66978db8689f52e97dcb4b..5e304be489703c740bac1b8b5ba53b67a295bdbe 100644
--- a/src/java/org/jastadd/Configuration.java
+++ b/src/java/org/jastadd/Configuration.java
@@ -408,6 +408,10 @@ public class Configuration {
       .templateVariable("EmptyContainerSingletons")
       .nonStandard();
 
+  public final Option<Boolean> optimizeImports = new FlagOption("optimize-imports",
+      "remove unused imports in generated code")
+      .nonStandard();
+
   /**
    * Indicates if there were unknown command-line options
    */
@@ -494,6 +498,9 @@ public class Configuration {
     allOptions.add(numThreadsOption);
     allOptions.add(concurrentMap);
 
+    // New since 2.3.4
+    allOptions.add(optimizeImports);
+
     // Deprecated in 2.1.5.
     allOptions.add(doxygenOption);
     allOptions.add(cacheAllOption);
diff --git a/src/java/org/jastadd/JastAddTask.java b/src/java/org/jastadd/JastAddTask.java
index 172e2d36fd292de6ed84e8b1d01a5e69758f9091..d5344538e8870f7413896ccb0fc51563ef2b5b54 100644
--- a/src/java/org/jastadd/JastAddTask.java
+++ b/src/java/org/jastadd/JastAddTask.java
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2013, The JastAdd Team
+/* Copyright (c) 2005-2019, The JastAdd Team
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -298,6 +298,10 @@ public class JastAddTask extends Task {
     setOption(config.emptyContainerSingletons, enable);
   }
 
+  public void setOptimizeImports(boolean enable) {
+    setOption(config.optimizeImports, enable);
+  }
+
   @Override
   public void execute() throws BuildException {
     System.err.println("generating node types and weaving aspects");
diff --git a/src/javacc/jrag/Unparser.java b/src/javacc/jrag/Unparser.java
index 56af94875a2274749d71daffa956615e9bb0047b..fa1bca6fd93fe668e785cd4d94bd4d34289221bc 100644
--- a/src/javacc/jrag/Unparser.java
+++ b/src/javacc/jrag/Unparser.java
@@ -4,18 +4,20 @@ import java.util.Set;
 import java.util.LinkedHashSet;
 
 import org.jastadd.jrag.AST.*;
+import org.jastadd.ast.AST.ImportDecl;
+import org.jastadd.ast.AST.MultiImportDecl;
 
 public class Unparser implements JragParserVisitor {
 
-  public static Set getImports(ASTCompilationUnit self) {
-    Set imports = new LinkedHashSet();
+  public static Set<ImportDecl> getImports(ASTCompilationUnit self) {
+    Set<ImportDecl> imports = new LinkedHashSet<ImportDecl>();
     for (int i = 0; i < self.jjtGetNumChildren(); i++) {
       Unparser.getImports((SimpleNode) self.jjtGetChild(i), imports);
     }
     return imports;
   }
 
-  public static void getImports(SimpleNode self, Set imports) {
+  public static void getImports(SimpleNode self, Set<ImportDecl> imports) {
     if (self instanceof ASTImportDeclaration) {
       Token t = new Token();
       t.next = self.firstToken;
@@ -27,7 +29,18 @@ public class Unparser implements JragParserVisitor {
         }
         buf.append(Util.addUnicodeEscapes(t.image));
       }
-      imports.add(buf.toString().trim());
+      String decl = buf.toString().trim();
+      String modifier = "";
+      if (decl.startsWith("import static")) {
+        modifier = " static";
+      }
+      String name = decl.substring(7 + modifier.length());
+      name = name.substring(0, name.length()-1);
+      if (name.endsWith(".*")) {
+        imports.add(new MultiImportDecl(modifier, name));
+      } else {
+        imports.add(new ImportDecl(modifier, name));
+      }
     }
   }