diff --git a/.gitignore b/.gitignore
index b2f15ce895696fd311b35bd9ebb831c094d75ac0..57f16fb67c1b1589981416b323d7a9debc728665 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,41 @@
-.idea/
-.settings/
-build/
+/build*
+/workspace
+setting.xml
+release/
 target/
-bin/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+icy.log
+
+### IntelliJ IDEA ###
+.idea/
+*.iws
 *.iml
-*.jar
+*.ipr
+
+### Eclipse ###
+.apt_generated
 .classpath
+.factorypath
 .project
-export.jardesc
-**/.DS_Store
\ No newline at end of file
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+**/.DS_Store
+Icon?
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b9332fabaeb52ec4c5e619c0b0ae59ef73e1a28c..05f4fb1e79d7244e1ca20be5e0a38fc94b5644b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,46 +7,36 @@
     <parent>
         <groupId>org.bioimageanalysis.icy</groupId>
         <artifactId>pom-icy</artifactId>
-        <version>2.2.0</version>
+        <version>3.0.0-a.1</version>
     </parent>
 
     <artifactId>workbooks</artifactId>
-    <version>4.0.0</version>
-
-    <packaging>jar</packaging>
+    <version>4.0.0-a.1</version>
 
     <name>Workbooks</name>
     <description>
         Toolbox to create and manipulate result tables with plug-ins and protocols, and export as text file or to your favorite spreadsheet software
     </description>
 
-    <properties>
-        <artifact-to-extract>JMathPlot,poi,poi-ooxml,poi-ooxml-lite,log4j-api,log4j-core</artifact-to-extract>
-    </properties>
-
     <dependencies>
         <dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
-            <artifactId>protocols</artifactId>
+            <artifactId>ezplug</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.bioimageanalysis.icy</groupId>
-            <artifactId>apache-poi</artifactId>
+            <artifactId>protocols</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>com.github.yannrichet</groupId>
-            <artifactId>JMathPlot</artifactId>
-            <version>1.0.1</version>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>icy-jmath</artifactId>
         </dependency>
     </dependencies>
 
     <repositories>
         <repository>
             <id>icy</id>
-            <name>Icy's Nexus</name>
-            <url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+            <url>https://nexus-icy.pasteur.cloud/repository/icy/</url>
         </repository>
     </repositories>
 </project>
\ No newline at end of file
diff --git a/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java b/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java
index a571dc7fa006b368d1e1224e49a2a3c007e4919a..7a3c7c7881869201a432c2970db0d12517dd17ba 100644
--- a/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java
+++ b/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -18,8 +18,11 @@
 
 package plugins.adufour.blocks.tools.io;
 
-import icy.plugin.abstract_.Plugin;
 import org.apache.poi.ss.usermodel.*;
+import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.jetbrains.annotations.NotNull;
 import plugins.adufour.blocks.tools.io.WorkbookToFile.MergePolicy;
 import plugins.adufour.blocks.util.VarList;
 import plugins.adufour.vars.lang.VarFile;
@@ -36,6 +39,8 @@ import java.io.FileReader;
  *
  * @author Alexandre Dufour
  */
+@IcyPluginName("File to Workbook")
+@IcyPluginIcon(path = "/workbooks.png")
 public class FileToWorkbook extends Plugin implements IOBlock {
     VarFile inputFile = new VarFile("input file", null);
     VarWorkbook workbook = new VarWorkbook("workbook", (Workbook) null);
@@ -46,12 +51,12 @@ public class FileToWorkbook extends Plugin implements IOBlock {
     }
 
     @Override
-    public void declareInput(final VarList inputMap) {
+    public void declareInput(final @NotNull VarList inputMap) {
         inputMap.add("input file", inputFile);
     }
 
     @Override
-    public void declareOutput(final VarList outputMap) {
+    public void declareOutput(final @NotNull VarList outputMap) {
         outputMap.add("workbook", workbook);
     }
 
@@ -70,7 +75,7 @@ public class FileToWorkbook extends Plugin implements IOBlock {
      * @param file input file
      * @return the workbook read from input file
      */
-    public static Workbook readWorkbook(final File file) {
+    public static Workbook readWorkbook(final @NotNull File file) {
         if (!file.exists() || file.isDirectory())
             throw new IllegalArgumentException("Cannot read a workbook from " + file.getPath());
 
diff --git a/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java b/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java
index 7e22a20fa3b1cbb46c027d82a72254bc8045c3e1..cc1b522436dcecb8ba96078684b4c356cc5837d8 100644
--- a/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java
+++ b/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -18,10 +18,13 @@
 
 package plugins.adufour.blocks.tools.io;
 
-import icy.file.FileUtil;
-import icy.plugin.abstract_.Plugin;
-import icy.system.IcyHandledException;
 import org.apache.poi.ss.usermodel.*;
+import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.bioimageanalysis.icy.io.FileUtil;
+import org.bioimageanalysis.icy.system.IcyHandledException;
+import org.jetbrains.annotations.NotNull;
 import plugins.adufour.blocks.util.VarList;
 import plugins.adufour.vars.lang.VarEnum;
 import plugins.adufour.vars.lang.VarFile;
@@ -36,6 +39,8 @@ import java.util.HashMap;
  *
  * @author Alexandre Dufour
  */
+@IcyPluginName("Workbook to File")
+@IcyPluginIcon(path = "/workbooks.png")
 public class WorkbookToFile extends Plugin implements IOBlock {
     public enum WorkbookFormat {
         /**
@@ -67,7 +72,7 @@ public class WorkbookToFile extends Plugin implements IOBlock {
         Merge_sheets___excluding_first_row;
 
         @Override
-        public String toString() {
+        public @NotNull String toString() {
             return super.toString().replace("__", ",").replace("_", " ");
         }
     }
@@ -93,7 +98,7 @@ public class WorkbookToFile extends Plugin implements IOBlock {
     }
 
     @Override
-    public void declareInput(final VarList inputMap) {
+    public void declareInput(final @NotNull VarList inputMap) {
         inputMap.add("workbook", workbook);
         inputMap.add("file format", format);
         inputMap.add("output file", file);
diff --git a/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java b/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java
index 0c6e326923d085a711f2270bb0c099729744dc7d..b70913045478ee5c1a6d8d9070c9aecae2ddb89e 100644
--- a/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java
+++ b/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -49,7 +49,6 @@ public class PersistentColumnControlButton extends ColumnControlButton {
         return new DefaultColumnControlPopup() {
             @Override
             public void addVisibilityActionItems(final List<? extends AbstractActionExt> actions) {
-
                 final ActionContainerFactory factory = new ActionContainerFactory(null);
                 for (final Action action : actions) {
                     final JMenuItem mi = factory.createMenuItem(action);
diff --git a/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java b/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java
index 9e357f62c6c2bef4dbcbb1605ff8a9c4d8c30b79..af0cf9cfd6e41f8803d7413b90b488a80b7bf7dd 100644
--- a/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java
+++ b/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -18,19 +18,23 @@
 
 package plugins.adufour.vars.gui.swing;
 
-import icy.gui.dialog.MessageDialog;
-import icy.gui.dialog.OpenDialog;
-import icy.gui.frame.IcyFrame;
-import icy.math.ArrayMath;
-import icy.resource.ResourceUtil;
-import icy.system.IcyHandledException;
-import icy.system.thread.ThreadUtil;
 import org.apache.poi.ss.formula.FormulaParseException;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.bioimageanalysis.icy.common.math.ArrayMath;
+import org.bioimageanalysis.icy.gui.component.button.IcyButton;
+import org.bioimageanalysis.icy.gui.component.icon.SVGIcon;
+import org.bioimageanalysis.icy.gui.dialog.MessageDialog;
+import org.bioimageanalysis.icy.gui.dialog.OpenDialog;
+import org.bioimageanalysis.icy.gui.frame.IcyFrame;
+import org.bioimageanalysis.icy.system.IcyHandledException;
+import org.bioimageanalysis.icy.system.thread.ThreadUtil;
 import org.jdesktop.swingx.JXPanel;
 import org.jdesktop.swingx.JXTable;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.math.plot.Plot2DPanel;
 import org.math.plot.Plot3DPanel;
 import org.math.plot.utils.Array;
@@ -196,7 +200,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
         this.readOnly.setValue(readOnly);
     }
 
-    private MergePolicy confirm() {
+    private @Nullable MergePolicy confirm() {
         final EzDialog confirmDialog = new EzDialog("Confirmation");
 
         confirmDialog.addEzComponent(new EzLabel("This file already exists."));
@@ -249,7 +253,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
             optionBar.setLayout(new BoxLayout(optionBar, BoxLayout.X_AXIS));
 
             // Open
-            openButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.ICON_OPEN, 18));
+            openButton = new IcyButton(SVGIcon.FILE_OPEN);
             openButton.setVisible(openButtonVisible.getValue());
             openButton.setBorderPainted(false);
             openButton.setFocusable(false);
@@ -270,7 +274,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
             optionBar.add(openButton);
 
             // Export
-            exportButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.ICON_SAVE, 18));
+            exportButton = new IcyButton(SVGIcon.SAVE);
             exportButton.setBorderPainted(false);
             exportButton.setFocusable(false);
             exportButton.setContentAreaFilled(false);
@@ -320,7 +324,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
 
             // Plot (START)
 
-            plotButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.getAlphaIconAsImage("align_left.png"), 18));
+            plotButton = new IcyButton(SVGIcon.FORMAT_ALIGN_LEFT);
             plotButton.setBorderPainted(false);
             plotButton.setFocusable(false);
             plotButton.setContentAreaFilled(false);
@@ -429,8 +433,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
                                 continue;
 
                             if (xValues.length != yValues.length)
-                                throw new IcyHandledException(
-                                        "Cannot create scatter plot: datasets have different sizes");
+                                throw new IcyHandledException("Cannot create scatter plot: datasets have different sizes");
 
                             final double minY = ArrayMath.min(yValues);
                             final double maxY = ArrayMath.max(yValues);
@@ -747,7 +750,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
             tabs.addTab("+", new JPanel());
     }
 
-    private static void createSheet(final Workbook book) {
+    private static void createSheet(final @NotNull Workbook book) {
         book.createSheet("Sheet " + (book.getNumberOfSheets() + 1));
     }
 
@@ -782,8 +785,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
         final VarString sheetName = new VarString("sheet name", sheet.getSheetName());
 
         final JXTable table = new JXTable(tableModel) {
+            @Contract("_, _ -> new")
             @Override
-            public TableCellEditor getCellEditor(final int row, final int column) {
+            public @NotNull TableCellEditor getCellEditor(final int row, final int column) {
                 return new DefaultCellEditor(new JTextField()) {
                     @Override
                     public Component getTableCellEditorComponent(final JTable theTable, final Object value, final boolean isSelected, int theRow, int theColumn) {
@@ -800,8 +804,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
                 };
             }
 
+            @Contract("_, _ -> new")
             @Override
-            public TableCellRenderer getCellRenderer(final int row, final int column) {
+            public @NotNull TableCellRenderer getCellRenderer(final int row, final int column) {
                 return new DefaultTableCellRenderer() {
                     @Override
                     public Component getTableCellRendererComponent(final JTable theTable, final Object value, final boolean isSelected, final boolean hasFocus, int theRow, int theColumn) {
@@ -865,13 +870,15 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
         // http://stackoverflow.com/questions/6711877/jtable-use-row-numbers
 
         final AbstractTableModel headerModel = new AbstractTableModel() {
+            @Contract(pure = true)
             @Override
             public int getColumnCount() {
                 return 1;
             }
 
+            @Contract(pure = true)
             @Override
-            public Object getValueAt(final int row, final int column) {
+            public @NotNull Object getValueAt(final int row, final int column) {
                 return row + 1;
             }
 
@@ -882,8 +889,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
         };
 
         final JXTable headerTable = new JXTable(headerModel) {
+            @Contract(value = "_, _ -> new", pure = true)
             @Override
-            public TableCellRenderer getCellRenderer(final int row, final int column) {
+            public @NotNull TableCellRenderer getCellRenderer(final int row, final int column) {
                 return new TableCellRenderer() {
                     private final TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
 
@@ -953,7 +961,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
 
         class Listener implements ListSelectionListener, TableColumnModelListener {
             @Override
-            public void valueChanged(final ListSelectionEvent e) {
+            public void valueChanged(final @NotNull ListSelectionEvent e) {
                 if (e.getValueIsAdjusting())
                     return;
 
@@ -969,18 +977,22 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
                 valueChanged(e);
             }
 
+            @Contract(pure = true)
             @Override
             public void columnRemoved(final TableColumnModelEvent e) {
             }
 
+            @Contract(pure = true)
             @Override
             public void columnMoved(final TableColumnModelEvent e) {
             }
 
+            @Contract(pure = true)
             @Override
             public void columnMarginChanged(final ChangeEvent e) {
             }
 
+            @Contract(pure = true)
             @Override
             public void columnAdded(final TableColumnModelEvent e) {
             }
@@ -1077,9 +1089,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> {
 
         @Override
         public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
-            if (aValue instanceof String) {
-                final String text = (String) aValue;
-
+            if (aValue instanceof final String text) {
                 if (text.isEmpty()) {
                     sheet.deleteCell(rowIndex, columnIndex);
                 }
diff --git a/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java b/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java
index 7f76ccff3db231c814018e0fab9ca43e36cc12ac..e080b9f71faf22c30aba335d69cea4c1aa48e498 100644
--- a/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java
+++ b/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -21,6 +21,7 @@ package plugins.adufour.vars.lang;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.jetbrains.annotations.NotNull;
 import plugins.adufour.vars.gui.VarEditor;
 import plugins.adufour.vars.gui.swing.WorkbookEditor;
 import plugins.adufour.vars.util.VarException;
@@ -86,7 +87,7 @@ public class VarWorkbook extends Var<Workbook> {
      * @throws VarException if the workbook is <code>null</code>
      * @throws IOException  if the file cannot be accessed
      */
-    public void saveToDisk(final File folder, final String workbookName) throws VarException, IOException {
+    public void saveToDisk(final @NotNull File folder, final String workbookName) throws VarException, IOException {
         if (!folder.isDirectory())
             throw new FilerException(folder + "is not a valid folder");
 
diff --git a/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java b/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java
index 8495c691f9d56b304798b5ae330fbe60e3af79de..a3dd6fc3daabdbb19b079777ff630c8750cba5a5 100644
--- a/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java
+++ b/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -27,6 +27,9 @@ import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap;
 import org.apache.poi.xssf.usermodel.XSSFCellStyle;
 import org.apache.poi.xssf.usermodel.XSSFColor;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.xmlbeans.impl.values.XmlValueDisconnectedException;
+import org.bioimageanalysis.icy.system.logging.IcyLogger;
+import org.jetbrains.annotations.NotNull;
 
 import java.awt.Color;
 import java.lang.reflect.Array;
@@ -48,7 +51,7 @@ public class IcySpreadSheet {
 
     private final Map<Color, CellStyle> colorStyleMap;
 
-    public IcySpreadSheet(final Sheet sheet) {
+    public IcySpreadSheet(final @NotNull Sheet sheet) {
         this.sheet = sheet;
         this.evaluator = sheet.getWorkbook().getCreationHelper().createFormulaEvaluator();
         this.colorStyleMap = new HashMap<>();
@@ -130,7 +133,12 @@ public class IcySpreadSheet {
         final Cell cell = row.getCell(columnIndex);
         if (cell != null) {
             row.removeCell(cell);
-            evaluator.notifyDeleteCell(cell);
+            try {
+                evaluator.notifyDeleteCell(cell);
+            }
+            catch (final XmlValueDisconnectedException e) {
+                IcyLogger.warn(this.getClass(), e); // Should not appear
+            }
         }
     }
 
@@ -411,9 +419,7 @@ public class IcySpreadSheet {
 
         // what is the value like?
 
-        if (value instanceof String) {
-            final String text = (String) value;
-
+        if (value instanceof final String text) {
             try {
                 value = Double.parseDouble(text);
             }
@@ -513,7 +519,7 @@ public class IcySpreadSheet {
      *                 converted to string using {@link Object#toString()}, while <code>null</code>
      *                 values will result in an empty cell
      */
-    public void setRow(final int rowIndex, final Object... values) {
+    public void setRow(final int rowIndex, final Object @NotNull ... values) {
         final Iterable<?> list;
 
         // special case: rowValues could contain a single array
diff --git a/src/main/java/plugins/adufour/workbooks/Workbooks.java b/src/main/java/plugins/adufour/workbooks/Workbooks.java
index 096be83d4df9cf0447e224a49672e0ef97b41f98..812243731a0a4e14d7939248df786cd63df74085 100644
--- a/src/main/java/plugins/adufour/workbooks/Workbooks.java
+++ b/src/main/java/plugins/adufour/workbooks/Workbooks.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2023. Institut Pasteur.
+ * Copyright (c) 2010-2024. Institut Pasteur.
  *
  * This file is part of Icy.
  * Icy is free software: you can redistribute it and/or modify
@@ -18,14 +18,19 @@
 
 package plugins.adufour.workbooks;
 
-import icy.plugin.abstract_.PluginActionable;
-import icy.plugin.interface_.PluginThreaded;
-import icy.system.thread.ThreadUtil;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.usermodel.Font;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.WorkbookUtil;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.bioimageanalysis.icy.extension.plugin.abstract_.PluginActionable;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon;
+import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName;
+import org.bioimageanalysis.icy.extension.plugin.interface_.PluginThreaded;
+import org.bioimageanalysis.icy.system.logging.IcyLogger;
+import org.bioimageanalysis.icy.system.thread.ThreadUtil;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
 import plugins.adufour.blocks.tools.io.FileToWorkbook;
 import plugins.adufour.blocks.tools.io.WorkbookToFile;
 import plugins.adufour.blocks.tools.io.WorkbookToFile.MergePolicy;
@@ -45,6 +50,8 @@ import java.util.List;
  *
  * @author Alexandre Dufour
  */
+@IcyPluginName("Workbooks")
+@IcyPluginIcon(path = "/workbooks.png")
 public class Workbooks extends PluginActionable implements PluginThreaded {
     /**
      * List of supported workbook formats
@@ -54,7 +61,9 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
     public enum WorkbookFormat {
         /**
          * Legacy format (compatible with Excel &lt;= 2004). Limited to 256 columns and 65536 rows
+         * Do not use it anymore
          */
+        @Deprecated(forRemoval = true)
         XLS,
 
         /**
@@ -62,24 +71,29 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
          */
         XLSX;
 
+        @Contract(" -> new")
+        @NotNull
         Workbook createEmptyWorkbook() {
-            switch (this) {
-                case XLS:
-                    return new HSSFWorkbook();
-                case XLSX:
-                    return new XSSFWorkbook();
-                default:
-                    throw new UnsupportedOperationException("Unknown format: " + this);
-            }
+            return switch (this) {
+                case XLS -> {
+                    IcyLogger.warn(this.getClass(), "Deprecated format, please use XLSX instead.");
+                    yield new HSSFWorkbook();
+                }
+                case XLSX -> new XSSFWorkbook();
+                //default -> throw new UnsupportedOperationException("Unknown format: " + this);
+            };
         }
 
-        public String getExtension() {
+        public @NotNull String getExtension() {
             return '.' + name().toLowerCase();
         }
 
+        @Contract(value = "null -> fail", pure = true)
         public static WorkbookFormat getFormat(final Workbook workbook) {
-            if (workbook instanceof HSSFWorkbook)
+            if (workbook instanceof HSSFWorkbook) {
+                IcyLogger.warn(WorkbookFormat.class, "Deprecated format, please use XLSX instead.");
                 return XLS;
+            }
             if (workbook instanceof XSSFWorkbook)
                 return XLSX;
 
@@ -87,7 +101,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
         }
     }
 
-    private final String defaultTitle = "Icy Workbooks v." + getDescriptor().getVersion().getMajor() + "." + getDescriptor().getVersion().getMinor();
+    private final String defaultTitle = "Icy Workbooks v." + getDescriptor().getVersion().toShortString();
 
     @Override
     public void run() {
@@ -97,7 +111,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
     /**
      * @return A new empty workbook using the new XLSX format.
      */
-    public static Workbook createEmptyWorkbook() {
+    public static @NotNull Workbook createEmptyWorkbook() {
         return createEmptyWorkbook(WorkbookFormat.XLSX);
     }
 
@@ -109,7 +123,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @see WorkbookFormat
      * @see WorkbookFormat#XLS
      */
-    public static Workbook createEmptyWorkbook(final WorkbookFormat format) {
+    public static @NotNull Workbook createEmptyWorkbook(final @NotNull WorkbookFormat format) {
         final Workbook workbook = format.createEmptyWorkbook();
         workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
         return workbook;
@@ -157,6 +171,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param wb input {@link Workbook}
      * @return The format of this workbook (see the {@link WorkbookFormat} enumeration)
      */
+    @Contract(value = "null -> fail", pure = true)
     public static WorkbookFormat getFormat(final Workbook wb) {
         return WorkbookFormat.getFormat(wb);
     }
@@ -175,7 +190,8 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @return the sheet with the specified name, wrapped into a {@link IcySpreadSheet} object for
      * simplified manipulation
      */
-    public static IcySpreadSheet getSheet(final Workbook workbook, String sheetName) {
+    @Contract("_, _ -> new")
+    public static @NotNull IcySpreadSheet getSheet(final @NotNull Workbook workbook, String sheetName) {
         Sheet sheet = workbook.getSheet(sheetName);
         if (sheet != null)
             return new IcySpreadSheet(sheet);
@@ -190,7 +206,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param workbook input {@link Workbook}
      * @return a collection of sheets contained in this workbook
      */
-    public static Collection<IcySpreadSheet> getSheets(final Workbook workbook) {
+    public static @NotNull Collection<IcySpreadSheet> getSheets(final @NotNull Workbook workbook) {
         final int nSheets = workbook.getNumberOfSheets();
         final List<IcySpreadSheet> sheets = new ArrayList<>(nSheets);
         for (int i = 0; i < nSheets; i++)
@@ -205,7 +221,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param srcCell source cell
      * @param dstCell destination cell
      */
-    public static void copyCell(final Cell srcCell, final Cell dstCell) {
+    public static void copyCell(final @NotNull Cell srcCell, final @NotNull Cell dstCell) {
         dstCell.setCellComment(srcCell.getCellComment());
         //dstCell.setCellType(srcCell.getCellType());
         switch (srcCell.getCellType()) {
@@ -234,7 +250,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param srcStyle source style
      * @param dstStyle destination style
      */
-    public static void copyStyle(final CellStyle srcStyle, final CellStyle dstStyle) {
+    public static void copyStyle(final @NotNull CellStyle srcStyle, final @NotNull CellStyle dstStyle) {
         dstStyle.setAlignment(srcStyle.getAlignment());
         dstStyle.setBorderBottom(srcStyle.getBorderBottom());
         dstStyle.setBorderLeft(srcStyle.getBorderLeft());
@@ -258,7 +274,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param srcFont source font
      * @param dstFont destination font
      */
-    public static void copyFont(final Font srcFont, final Font dstFont) {
+    public static void copyFont(final @NotNull Font srcFont, final @NotNull Font dstFont) {
         dstFont.setBold(srcFont.getBold());
         dstFont.setCharSet(srcFont.getCharSet());
         dstFont.setColor(srcFont.getColor());
@@ -280,7 +296,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded {
      * @param sheetName the name of the sheet to search for
      * @return <code>true</code> if the sheet exists, <code>false</code> otherwise
      */
-    public static boolean containsSheet(final Workbook workbook, String sheetName) {
+    public static boolean containsSheet(final @NotNull Workbook workbook, String sheetName) {
         sheetName = WorkbookUtil.createSafeSheetName(sheetName);
         return workbook.getSheet(sheetName) != null;
     }
diff --git a/src/main/resources/workbooks.png b/src/main/resources/workbooks.png
new file mode 100644
index 0000000000000000000000000000000000000000..275debf629d721e470b2759fdae3f67251b74dc4
Binary files /dev/null and b/src/main/resources/workbooks.png differ