diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/CommunityNetworkBuilder.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/CommunityNetworkBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..c14270c6d0f06e00257479cee568b94c1b707682
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/CommunityNetworkBuilder.java
@@ -0,0 +1,49 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class CommunityNetworkBuilder extends MetaNetworkBuilder implements MultiNetworkBuilder{
+
+    private BioCompartment medium;
+
+    public CommunityNetworkBuilder(){
+        setMedium();
+    }
+
+    public CommunityNetworkBuilder(BioCompartment medium){
+        this.medium=medium;
+        this.addNewSharedCompartment(medium);
+    }
+
+    public CommunityNetworkBuilder(Set<BioNetwork> networks){
+        setMedium();
+        Map<BioNetwork,String> prefixes = networks.stream()
+                .collect(Collectors.toMap(n->n,n->n.getId()+"_"));
+        this.setEntityFactory(new PrefixedMetaEntityFactory(prefixes,"pool"));
+        for(BioNetwork bn : networks){
+            this.add(bn);
+        }
+    }
+
+    private void setMedium(){
+        this.medium = new BioCompartment(UUID.randomUUID().toString(),"medium");
+        this.addNewSharedCompartment(medium);
+    }
+
+    public void exchangeWithMedium(BioNetwork sourceNetwork, BioCompartment sourceCompartment, BioMetabolite metabolite){
+        this.exchangeWithSharedCompartment(sourceNetwork, sourceCompartment, metabolite, medium);
+    };
+
+    public void add(BioNetwork bn, BioCompartment externalComp) {
+        super.add(bn);
+        this.fuseCompartmentIntoSharedCompartment(bn,externalComp,medium);
+    }
+
+}
diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaBioMetabolite.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaBioMetabolite.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d3bfce22439ed409274663402e56ee87c273eeb
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaBioMetabolite.java
@@ -0,0 +1,16 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+
+public record MetaBioMetabolite(BioMetabolite metabolite, BioCompartment sourceCompartment, BioNetwork sourceNetwork) {
+    public MetaBioMetabolite(BioMetabolite metabolite, BioCompartment sourceCompartment, BioNetwork sourceNetwork){
+        if(!sourceNetwork.containsMetabolite(metabolite.getId())) throw new IllegalArgumentException("Source network does not contains metabolites");
+        if(!sourceNetwork.containsCompartment(sourceCompartment.getId())) throw new IllegalArgumentException("Source network does not contains source compartment");
+        if(sourceCompartment.getComponentsView().get(metabolite.getId())==null) throw new IllegalArgumentException("Source compartment does not contains metabolites");
+        this.metabolite = metabolite;
+        this.sourceCompartment = sourceCompartment;
+        this.sourceNetwork = sourceNetwork;
+    }
+}
diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaEntityFactory.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaEntityFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b709b4ab21c7a8d31743a52edd3dcb25f556f74
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaEntityFactory.java
@@ -0,0 +1,40 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+
+import java.util.Collection;
+
+/**
+* Factory to create new instances of BioEntity and avoid id conflicts between subnetworks in a meta-graph.
+**/
+public interface MetaEntityFactory {
+
+    /**
+    * Create a copy of a subnetwork's BioEntity for its MetaGraph, while guaranteeing no id conflicts if the same
+    * original id was used in another subnetworks. This may be done by referencing in the new id the source network
+    * passed as parameter.
+    * @param originalEntity the original entity
+    * @param source the source network
+    **/
+    <E extends BioEntity> E createMetaEntity(E originalEntity, BioNetwork source);
+
+    /**
+     * Create a copy of a subnetwork's BioMetabolite in a shared compartment.
+     * @param originalEntity the original entity
+     * @param sharedComp the target shared compartment
+     * @return
+     */
+    BioMetabolite createSharedCompound(BioMetabolite originalEntity, BioCompartment sharedComp);
+
+    /**
+     * Create a new BioMetabolite in a shared compartment, that represent a pool of compounds from different subnetworks.
+     * @param entities the matching entities in different subnetworks
+     * @param sharedComp the target shared compartment
+     * @return
+     */
+    BioMetabolite createPoolCompound (Collection<BioMetabolite> entities, BioCompartment sharedComp);
+
+}
diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaNetworkBuilder.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaNetworkBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..6180d7b809e2305bc6b48674fbe16f5b57725cae
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MetaNetworkBuilder.java
@@ -0,0 +1,351 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.*;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollection;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * A class that creates a meta-network from multiple sub-networks. A meta-network is a single model which contains several
+ * sub-networks that remain individualized within the meta-network (as opposed to models fusion), but which can share
+ * some of their components with other sub-networks through shared compartments.
+ */
+public class MetaNetworkBuilder implements MultiNetworkBuilder{
+
+    private Map<BioMetabolite,BioMetabolite> metaboliteConversion;
+    private Map<BioCompartment,BioCompartment> compartmentConversion;
+    private Map<BioReaction,BioReaction> reactionConversion;
+    private Map<BioProtein,BioProtein> proteinConversion;
+    private Map<BioEnzyme,BioEnzyme> enzymeConversion;
+    private Map<BioGene,BioGene> geneConversion;
+
+    protected BioCollection<BioNetwork> networks = new BioCollection<>();
+    private Map<BioCompartment, Set<MetaBioMetabolite>> metaCompComposition = new HashMap<>();
+
+    @Setter
+    protected Function<BioMetabolite,String> getSharedIdFunction = BioEntity::getName;
+    @Setter
+    private String poolReactionPrefix = "poolReaction_";
+    @Setter
+    private String sharedTransportPrefix = "transport_";
+    @Setter
+    private String exchangeReactionPrefix = "exchange_";
+    @Setter
+    protected BiFunction<BioMetabolite, BioMetabolite, BioReaction> createLinkWithSharedPool = (m, pool) -> {
+        BioReaction r=new BioReaction(poolReactionPrefix+ m.getId());
+        r.setReversible(true);
+        return r;
+    };
+
+    @Setter
+    protected Function<BioMetabolite, BioReaction> createPoolExchangeReaction = (pool) -> {
+        BioReaction r=new BioReaction(exchangeReactionPrefix+pool.getId());
+        r.setReversible(true);
+        return r;
+    };
+
+    @Setter
+    protected BiFunction<BioMetabolite, BioCompartment, BioReaction> createTransportWithSharedComp = (m, comp) -> {
+        BioReaction r=new BioReaction(sharedTransportPrefix+ m.getId()+"_to_"+comp.getId());
+        r.setReversible(true);
+        return r;
+    };
+    @Setter
+    @Getter
+    protected MetaEntityFactory entityFactory;
+    protected HashMap<BioCompartment,BioCompartment> fuseMap= new HashMap<>();
+    protected Boolean keepGPR = false;
+    @Setter
+    protected Boolean addExchangeReaction = true;
+
+    /**
+     * enable a compartment to be shared between multiple organisms
+     * @param sc a compartment that will be shared between multiple bionetworks
+     */
+    public void addNewSharedCompartment(BioCompartment sc) {
+        metaCompComposition.put(sc,new HashSet<>());
+    }
+
+    /**
+     * Fuse a sub-network's compartment into a meta-network's shared compartment. The former will be replaced by the latter during build.
+     * All compartment's components will be added to the shared compartment
+     * @param n the sub-network
+     * @param c the sub-network's compartment to be fused
+     * @param sc the shared meta-compartment that will receive the compartment's components
+     */
+    public void fuseCompartmentIntoSharedCompartment(BioNetwork n, BioCompartment c, BioCompartment sc) {
+        if(!metaCompComposition.containsKey(sc)) throw new IllegalArgumentException("Shared meta-compartment "+sc.getId()+" not found in network");
+        fuseMap.put(c,sc);
+    }
+
+    /**
+     * Bump a sub-network's compartment into a meta-network's shared compartment. For each compartment's component,
+     * a copy is created within the shared compartment
+     * @param n the sub-network
+     * @param c the sub-network's compartment to be bumped
+     * @param sc the shared meta-compartment that will receive the compartment's components
+     */
+    public void bumpCompartmentIntoSharedCompartment(BioNetwork n, BioCompartment c, BioCompartment sc) {
+        BioCollection<BioMetabolite> toShare = n.getMetabolitesView().stream()
+                .filter(x -> n.getCompartmentsOf(x).contains(c))
+                .collect(BioCollection::new,BioCollection::add,BioCollection::addAll);
+        this.exchangeWithSharedCompartment(n,c,toShare,sc);
+    }
+
+    /**
+     * Exchange a compound from a sub-network's compartment to a meta-network's shared compartment. A copy of the compound will be created
+     * @param sourceNetwork the original subnetwork of the compound
+     * @param sourceCompartment  the original compartment of the compound in the subnetwork
+     * @param metabolite the compound
+     * @param sc the target shared meta-compartment that will receive the compound
+     */
+    public void exchangeWithSharedCompartment(BioNetwork sourceNetwork, BioCompartment sourceCompartment, BioMetabolite metabolite, BioCompartment sc) {
+        if(!metaCompComposition.containsKey(sc)) throw new IllegalArgumentException("Shared meta-compartment "+sc.getId()+" not found in network");
+        metaCompComposition.get(sc).add(new MetaBioMetabolite(metabolite,sourceCompartment,sourceNetwork));
+    }
+
+    /**
+     * Exchange compounds from a sub-network's compartment to a meta-network's shared compartment. A copy of each exchanged compound will be created
+     * @param sourceNetwork the original subnetwork of the compounds
+     * @param sourceCompartment  the original compartment of the compounds in the subnetwork
+     * @param metabolites the compounds
+     * @param sc the target shared meta-compartment that will receive the compounds
+     */
+    public void exchangeWithSharedCompartment(BioNetwork sourceNetwork, BioCompartment sourceCompartment, BioCollection<BioMetabolite> metabolites, BioCompartment sc) {
+        if(!metaCompComposition.containsKey(sc)) throw new IllegalArgumentException("Shared meta-compartment "+sc.getId()+" not found in network");
+        Set<MetaBioMetabolite> scCompo = metaCompComposition.get(sc);
+        for(BioMetabolite m : metabolites ){
+            scCompo.add(new MetaBioMetabolite(m,sourceCompartment,sourceNetwork));
+        }
+    }
+
+    @Override
+    public void add(BioNetwork bn) {
+        networks.add(bn);
+    }
+
+    /**
+     * creates all maps for conversion (original bioentity, new meta-entity)
+     * instantiate meta-network as new bionetwork
+     * @return an empty meta-network
+     */
+    protected BioNetwork initMetaNetwork(){
+        this.metaboliteConversion = new HashMap<>();
+        this.compartmentConversion = new HashMap<>();
+        this.reactionConversion = new HashMap<>();
+        this.proteinConversion = new HashMap<>();
+        this.enzymeConversion = new HashMap<>();
+        this.geneConversion = new HashMap<>();
+        return new BioNetwork();
+    }
+
+    /**
+     * add all copied bioentity to the newly built meta-network
+     * @param meta the (usually empty) meta-network
+     */
+    protected void populateMetaNetwork(BioNetwork meta){
+
+        for(BioNetwork sub : networks){
+
+            for(BioMetabolite e : sub.getMetabolitesView()){
+                BioMetabolite e2 = entityFactory.createMetaEntity(e,sub);
+                meta.add(e2);
+                metaboliteConversion.put(e,e2);
+            }
+            for(BioCompartment c : sub.getCompartmentsView()){
+                BioCompartment c2 = fuseMap.get(c);
+                if(c2==null){
+                    c2 = entityFactory.createMetaEntity(c,sub);
+                    meta.add(c2);
+                }
+                compartmentConversion.put(c,c2);
+                BioCollection<BioMetabolite> content = c.getComponentsView().stream()
+                        .filter((e) -> e.getClass().equals(BioMetabolite.class))
+                        .map(metaboliteConversion::get)
+                        .collect(BioCollection::new,BioCollection::add,BioCollection::addAll);
+
+                meta.affectToCompartment(c2, content);
+            }
+
+            // Copy genes
+            if (keepGPR) {
+                //TODO case shared Genome
+                for (BioGene gene : sub.getGenesView()) {
+                    BioGene newGene = entityFactory.createMetaEntity(gene,sub);
+                    meta.add(newGene);
+                    geneConversion.put(gene,newGene);
+                }
+                for (BioProtein protein : sub.getProteinsView()) {
+                    BioProtein newProtein = entityFactory.createMetaEntity(protein,sub);
+                    meta.add(newProtein);
+                    proteinConversion.put(protein,newProtein);
+
+                    if (protein.getGene() != null) {
+                        BioGene newGene = geneConversion.get(protein.getGene());
+                        meta.affectGeneProduct(newProtein, newGene);
+                    }
+                }
+                for (BioEnzyme enzyme : sub.getEnzymesView()) {
+
+                    BioEnzyme newEnzyme = entityFactory.createMetaEntity(enzyme,sub);
+                    meta.add(newEnzyme);
+                    enzymeConversion.put(enzyme,newEnzyme);
+
+                    BioCollection<BioEnzymeParticipant> participants = enzyme.getParticipantsView();
+
+                    for (BioEnzymeParticipant participant : participants) {
+                        Double quantity = participant.getQuantity();
+
+                        if (participant.getPhysicalEntity().getClass().equals(BioMetabolite.class)) {
+                            BioMetabolite metabolite = (BioMetabolite) participant.getPhysicalEntity();
+                            BioMetabolite newMetabolite = metaboliteConversion.get(metabolite);
+                            meta.affectSubUnit(newEnzyme, quantity, newMetabolite);
+                        } else if (participant.getPhysicalEntity().getClass().equals(BioProtein.class)) {
+                            BioProtein protein = (BioProtein) participant.getPhysicalEntity();
+                            BioProtein newProtein  = proteinConversion.get(protein);
+                            meta.affectSubUnit(newEnzyme, quantity, newProtein);
+                        }
+                    }
+                }
+            }
+
+            for (BioReaction r : sub.getReactionsView()) {
+
+                BioReaction newReaction = entityFactory.createMetaEntity(r,sub);
+                newReaction.setSpontaneous(r.isSpontaneous());
+                newReaction.setReversible(r.isReversible());
+                newReaction.setEcNumber(r.getEcNumber());
+
+                meta.add(newReaction);
+                reactionConversion.put(r,newReaction);
+
+                // Copy lefts
+                for (BioReactant reactant : r.getLeftReactantsView()) {
+                    BioMetabolite newMetabolite = metaboliteConversion.get(reactant.getMetabolite());
+                    BioCompartment newCpt = compartmentConversion.get(reactant.getLocation());
+                    Double sto = reactant.getQuantity();
+                    meta.affectLeft(newReaction, sto, newCpt, newMetabolite);
+                }
+
+                // Copy rights
+                for (BioReactant reactant : r.getRightReactantsView()) {
+                    BioMetabolite newMetabolite = metaboliteConversion.get(reactant.getMetabolite());
+                    BioCompartment newCpt = compartmentConversion.get(reactant.getLocation());
+                    Double sto = reactant.getQuantity();
+                    meta.affectRight(newReaction, sto, newCpt, newMetabolite);
+                }
+
+                // Copy enzymes
+                if (keepGPR) {
+                    for (BioEnzyme enzyme : r.getEnzymesView()) {
+                        BioEnzyme newEnzyme = enzymeConversion.get(enzyme);
+                        meta.affectEnzyme(newReaction, newEnzyme);
+                    }
+                }
+            }
+
+            for (BioPathway pathway : sub.getPathwaysView()) {
+                BioPathway newPathway = entityFactory.createMetaEntity(pathway,sub);
+                meta.add(newPathway);
+                // Add reactions into pathway
+                BioCollection<BioReaction> reactions = sub.getReactionsFromPathways(pathway);
+
+                for (BioReaction reaction : reactions) {
+                    BioReaction newReaction = reactionConversion.get(reaction);
+                    meta.affectToPathway(newPathway, newReaction);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the alias prefixes for the entities of the sub-networks. This allows to distinguish entities from different sub-networks
+     * that have the same name. The prefixes are added to the entity names in the meta-network.
+     * @param aliases a map of sub-networks and their respective prefixes
+     *                (e.g. {subNetwork1: "prefix1", subNetwork2: "prefix2"})
+     *                The prefixes are added to the entity names in the meta-network.
+     *                If a sub-network is not in the map, or is in the map but has a null or empty prefix, its entities will not be prefixed.
+     **/
+    public void setAliasPrefixes(Map<BioNetwork,String> aliases, String poolPrefix){
+        this.setEntityFactory(new PrefixedMetaEntityFactory(aliases, poolPrefix));
+    }
+
+    @Override
+    public BioNetwork build() {
+        BioNetwork meta = this.initMetaNetwork();
+        //create shared compartment
+        this.initSharedComp(meta);
+        //add each subnetwork content
+        this.populateMetaNetwork(meta);
+        //add shared compartment components and exchanges
+        this.populateSharedComp(meta);
+        //link compounds in shared compartment
+        this.linkCompoundsInSharedComp(meta);
+        return meta;
+    }
+    protected void initSharedComp(BioNetwork meta) {
+        for (Map.Entry<BioCompartment, Set<MetaBioMetabolite>> compDescriptor : metaCompComposition.entrySet()) {
+            BioCompartment sc = compDescriptor.getKey();
+            meta.addCompartment(sc);
+        }
+    }
+
+    protected void populateSharedComp(BioNetwork meta){
+        for(Map.Entry<BioCompartment, Set<MetaBioMetabolite>> compDescriptor : metaCompComposition.entrySet()){
+            for(MetaBioMetabolite m : compDescriptor.getValue()){
+                BioMetabolite m1 = metaboliteConversion.get(m.metabolite());
+                BioMetabolite m2 = entityFactory.createSharedCompound(m1,compDescriptor.getKey());
+                meta.add(m2);
+                meta.affectToCompartment(compDescriptor.getKey(),m2);
+                BioReaction t = createTransportWithSharedComp.apply(m1,compDescriptor.getKey());
+                meta.add(t);
+                meta.affectLeft(t,1.0,compartmentConversion.get(m.sourceCompartment()),metaboliteConversion.get(m.metabolite()));
+                meta.affectLeft(t,1.0,compDescriptor.getKey(),m2);
+            }
+        }
+    }
+
+    /**
+     * Links the source-specific metabolites representing the same compounds in the shared compartments, by creating a shared "pool" entity
+     * alongside their interconversion reactions.
+     * @param meta the meta-network
+     */
+    protected void linkCompoundsInSharedComp(BioNetwork meta) {
+        //loop over each shared compartment
+        for(BioCompartment sharedComp : metaCompComposition.keySet()){
+            //retrieve all metabolite components that has been previously set, using whole compartment fusing or individual additions
+            BioCollection<BioMetabolite> content = sharedComp.getComponentsView().stream()
+                    .filter((e) -> e.getClass().equals(BioMetabolite.class))
+                    .map(o -> (BioMetabolite)o)
+                    .collect(BioCollection::new,BioCollection::add,BioCollection::addAll);
+
+            //all entities that represent the same compounds from different source are grouped
+            Map<String, List<BioMetabolite>> compoundGroups = content.stream().collect(Collectors.groupingBy(getSharedIdFunction));
+
+            //for each group, create a "pool" metabolite that represent them
+            for(Map.Entry<String, List<BioMetabolite>> group : compoundGroups.entrySet()){
+                BioMetabolite pool = this.entityFactory.createPoolCompound(group.getValue(),sharedComp);
+                meta.add(pool);
+                meta.affectToCompartment(sharedComp,pool);
+                //for each member of the group, create a reversible reaction linking them to their "pool" counterpart
+                for(BioMetabolite e : group.getValue()){
+                    BioReaction r = this.createLinkWithSharedPool.apply(e,pool);
+                    meta.add(r);
+                    meta.affectRight(r,1.0, sharedComp,e);
+                    meta.affectLeft(r,1.0, sharedComp,pool);
+                }
+                //if option selected, add exchange reaction to the pool for flux modeling
+                if(addExchangeReaction){
+                    BioReaction r = this.createPoolExchangeReaction.apply(pool);
+                    meta.add(r);
+                    meta.affectLeft(r,1.0, sharedComp,pool);
+                }
+            }
+        }
+    }
+}
diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MultiNetworkBuilder.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MultiNetworkBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..12f084efae29005006d84a27e1238f79d74b57bf
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/MultiNetworkBuilder.java
@@ -0,0 +1,20 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+
+public interface MultiNetworkBuilder {
+
+    /**
+     * add network into meta-network
+     * @param bn
+     */
+    public void add(BioNetwork bn);
+
+    /**
+     * create a new network which encompass all added networks
+     * @return
+     */
+    public BioNetwork build();
+
+
+}
diff --git a/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/PrefixedMetaEntityFactory.java b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/PrefixedMetaEntityFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..29437aa064cda40ad95fbbd32352ec69c0c394f9
--- /dev/null
+++ b/met4j-core/src/main/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/PrefixedMetaEntityFactory.java
@@ -0,0 +1,100 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+import lombok.Setter;
+import lombok.SneakyThrows;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import static fr.inrae.toulouse.metexplore.met4j_core.utils.StringUtils.isVoid;
+
+/**
+ * Factory to create new instances of BioEntity using prefixed ids from user-defined alias, in order to avoid id conflicts between subnetworks
+ * in a meta-graph.
+ **/
+public class PrefixedMetaEntityFactory implements MetaEntityFactory {
+
+    Map<BioNetwork,String> sourcePrefixMap;
+    @Setter
+    protected String poolPrefix;
+    @Setter
+    protected String sep = "_";
+    
+    public PrefixedMetaEntityFactory(Map<BioNetwork,String> sourcePrefixMap, String poolPrefix){
+        this.sourcePrefixMap=sourcePrefixMap;
+        this.poolPrefix=poolPrefix;
+    }
+
+    /**
+     * create prefixed id. This function will be called to create a new id for a BioEntity, using the
+     * alias of the network it comes from (if it exists) and its original id.
+     */
+    public BiFunction<String, BioNetwork, String> addSourcePrefix = (id, bn) -> isVoid(sourcePrefixMap.get(bn)) ? id : sourcePrefixMap.get(bn)+sep+id;
+
+    /**
+     * create sharedCompartment-suffix id
+     */
+    public BiFunction<String, BioCompartment, String> addCompSuffix = (id, comp) -> id+sep+comp.getId();
+    /**
+     * create sharedCompartment-prefix id
+     */
+    public BiFunction<String, BioCompartment, String> addCompPrefix = (id, comp) -> comp.getId()+sep+id;
+
+
+    /**
+     * remove prefix-id from network alias
+     */
+    public Function<String, String> removeSourceSuffix = id -> {
+            for(String s : sourcePrefixMap.values()) {
+                id = id.replaceAll("^" + s + sep,"");
+            };
+            return id;
+        };
+
+    /**
+     * create a pool-prefix id. This is used to identify a pool of compounds originating from different subnetworks.
+     */
+    public Function<String, String> addPoolFlag = (id) -> poolPrefix+sep+id;
+
+
+    @SneakyThrows
+    @Override
+    public <E extends BioEntity> E createMetaEntity(E originalEntity, BioNetwork source) {
+        return newEntityInstance(originalEntity, this.addSourcePrefix.apply(originalEntity.getId(),source));
+    }
+
+    @SneakyThrows
+    @Override
+    public BioMetabolite createSharedCompound(BioMetabolite entity, BioCompartment sharedCompartment) {
+        return newEntityInstance(entity, this.addCompSuffix.apply(entity.getId(),sharedCompartment));
+    }
+
+    @SneakyThrows
+    @Override
+    public BioMetabolite createPoolCompound(Collection<BioMetabolite> entities, BioCompartment sharedCompartment) {
+        BioMetabolite entity = entities.iterator().next();
+        return newEntityInstance(entity, removeSourceSuffix
+                .andThen(s -> addCompPrefix.apply(s,sharedCompartment))
+                .andThen(addPoolFlag)
+                .apply(entity.getId()));
+    }
+
+    private static <E extends BioEntity> E newEntityInstance(E entity, String newId) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        E newEntity = (E) entity.getClass().getDeclaredConstructor(String.class).newInstance(newId);
+        newEntity.setName(entity.getName());
+        newEntity.setSynonyms(new ArrayList<>(entity.getSynonyms()));
+        newEntity.setComment(entity.getComment());
+        newEntity.setRefs(new HashMap<>(entity.getRefs()));
+        newEntity.setAttributes(new HashMap<>(entity.getAttributes()));
+        return newEntity;
+    }
+}
diff --git a/met4j-core/src/test/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/TestMetaNetworkBuilder.java b/met4j-core/src/test/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/TestMetaNetworkBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..31d2bbd88ddc9d2f64da168194888af75520f5eb
--- /dev/null
+++ b/met4j-core/src/test/java/fr/inrae/toulouse/metexplore/met4j_core/biodata/multinetwork/TestMetaNetworkBuilder.java
@@ -0,0 +1,287 @@
+package fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioReaction;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+public class TestMetaNetworkBuilder {
+
+
+    public static BioNetwork bn1, bn2, bn3, bn4;
+    public static BioMetabolite a1,b1,c1,c1ex,c2,c2ex,d3, d3ex, f3, d4, d4ex;
+    public static BioCompartment co1in, co1ex, co2in, co2ex, co3in, co3ex,co4in, co4ex,medium,medium2;
+    public static BioReaction r1, r3, tc1, tc2, td3,td4;
+
+
+    public static BioNetwork initBump() {
+        HashMap<BioNetwork,String> alias = new HashMap<>();
+        alias.put(bn1,"model1");
+        alias.put(bn2,"model2");
+        alias.put(bn3,"model3");
+        alias.put(bn4,"model4");
+        medium=new BioCompartment("medium");
+        medium2=new BioCompartment("medium2");
+        CommunityNetworkBuilder builder = new CommunityNetworkBuilder(medium);
+        builder.addNewSharedCompartment(medium2);
+        PrefixedMetaEntityFactory factory = new PrefixedMetaEntityFactory(alias,"pool");
+        factory.setSep("_");
+        builder.setEntityFactory(factory);
+        builder.setAddExchangeReaction(false);
+        builder.add(bn1);
+        builder.add(bn2);
+        builder.add(bn3);
+        builder.add(bn4);
+        builder.bumpCompartmentIntoSharedCompartment(bn1,co1ex,medium);
+        builder.bumpCompartmentIntoSharedCompartment(bn2,co2ex,medium);
+        builder.bumpCompartmentIntoSharedCompartment(bn3,co3ex,medium);
+        builder.bumpCompartmentIntoSharedCompartment(bn4,co4ex,medium2);
+        builder.bumpCompartmentIntoSharedCompartment(bn4,co4ex,medium);
+        return builder.build();
+    }
+
+    public static BioNetwork initFuse() {
+        HashMap<BioNetwork,String> alias = new HashMap<>();
+        alias.put(bn1,"model1");
+        alias.put(bn2,"model2");
+        alias.put(bn3,"model3");
+        alias.put(bn4,"model4");
+        medium=new BioCompartment("medium");
+        medium2=new BioCompartment("medium2");
+        CommunityNetworkBuilder builder = new CommunityNetworkBuilder(medium);
+        builder.addNewSharedCompartment(medium2);
+        PrefixedMetaEntityFactory factory = new PrefixedMetaEntityFactory(alias,"pool");
+        factory.setSep("_");
+        builder.setEntityFactory(factory);
+        builder.setAddExchangeReaction(false);
+        builder.add(bn1,co1ex);
+        builder.add(bn2,co2ex);
+        builder.add(bn3,co3ex);
+        builder.add(bn4);
+        builder.fuseCompartmentIntoSharedCompartment(bn4,co4ex,medium2);
+        return builder.build();
+    }
+
+    public static BioNetwork initFuseIncremental() {
+        HashMap<BioNetwork,String> alias = new HashMap<>();
+        alias.put(bn1,"model1");
+        alias.put(bn2,"model2");
+
+        BioCompartment medium0=new BioCompartment("medium");
+        CommunityNetworkBuilder builder = new CommunityNetworkBuilder(medium0);
+        PrefixedMetaEntityFactory factory = new PrefixedMetaEntityFactory(alias,"pool");
+        factory.setSep("_");
+        builder.setEntityFactory(factory);
+        builder.setAddExchangeReaction(false);
+        builder.add(bn1,co1ex);
+        builder.add(bn2,co2ex);
+        BioNetwork firstIter = builder.build();
+
+        System.out.println(Arrays.toString(bn1.getMetabolitesView().stream().map(x -> x.getId()).toArray()));
+        System.out.println(Arrays.toString(bn1.getReactionsView().stream().map(x -> x.getId()).toArray()));
+        System.out.println("+");
+        System.out.println(Arrays.toString(bn2.getMetabolitesView().stream().map(x -> x.getId()).toArray()));
+        System.out.println(Arrays.toString(bn2.getReactionsView().stream().map(x -> x.getId()).toArray()));
+        System.out.println("=");
+        System.out.println(Arrays.toString(firstIter.getMetabolitesView().stream().map(x -> x.getId()).toArray()));
+        System.out.println(Arrays.toString(firstIter.getReactionsView().stream().map(x -> x.getId()).toArray()));
+
+        BioMetabolite[] pool1 = firstIter.getCompartment("medium")
+                .getComponentsView().stream()
+                .filter(e -> e instanceof BioMetabolite)
+                .filter(m -> m.getId().startsWith("pool_"))
+                .toArray(BioMetabolite[]::new);
+        for(BioMetabolite pool : pool1){
+            firstIter.removeOnCascade(firstIter.getReactionsFromMetabolite(pool));
+        }
+        firstIter.removeOnCascade(pool1);
+
+        System.out.println("->");
+        System.out.println(Arrays.toString(firstIter.getMetabolitesView().stream().map(x -> x.getId()).toArray()));
+        System.out.println(Arrays.toString(firstIter.getReactionsView().stream().map(x -> x.getId()).toArray()));
+
+        medium=new BioCompartment("medium");
+        medium2=new BioCompartment("medium2");
+        builder = new CommunityNetworkBuilder(medium);
+        builder.addNewSharedCompartment(medium2);
+        alias = new HashMap<>();
+        alias.put(firstIter,"");
+        alias.put(bn3,"model3");
+        alias.put(bn4,"model4");
+        builder.setEntityFactory(new PrefixedMetaEntityFactory(alias,"pool"));
+        builder.setAddExchangeReaction(false);
+        builder.add(firstIter,medium0);
+        builder.add(bn3,co3ex);
+        builder.add(bn4);
+        builder.fuseCompartmentIntoSharedCompartment(bn4,co4ex,medium2);
+
+        BioNetwork output = builder.build();
+        System.out.println(Arrays.toString(output.getMetabolitesView().stream().map(x -> x.getId()).toArray()));
+        System.out.println(Arrays.toString(output.getReactionsView().stream().map(x -> x.getId()).toArray()));
+        return output;
+    }
+
+    @BeforeClass
+    public static void beforeClass() {
+        bn1 = new BioNetwork("bn1");
+        bn2 = new BioNetwork("bn2");
+        bn3 = new BioNetwork("bn3");
+        bn4 = new BioNetwork("bn4");
+        a1 = new BioMetabolite("a_in","a");
+        b1 = new BioMetabolite("b_in","b");
+        c1 = new BioMetabolite("c_in","c");
+        c1ex = new BioMetabolite("c_ex","c");
+        r1 = new BioReaction("r1");
+        tc1 = new BioReaction("tc");
+        co1in = new BioCompartment("in");
+        co1ex = new BioCompartment("ex");
+        bn1.add(a1,b1,c1,c1ex,r1,tc1,co1in,co1ex);
+        bn1.affectToCompartment(co1in,a1,b1,c1);
+        bn1.affectToCompartment(co1ex,c1ex);
+        bn1.affectLeft(r1,1.0,co1in,a1);
+        bn1.affectLeft(r1,1.0,co1in,b1);
+        bn1.affectRight(r1,2.0,co1in,c1);
+        bn1.affectLeft(tc1,1.0,co1in,c1);
+        bn1.affectRight(tc1,1.0,co1ex,c1ex);
+        c2 = new BioMetabolite("c_in","c");
+        c2ex = new BioMetabolite("c_ex","c");
+        tc2 = new BioReaction("tc");
+        co2in = new BioCompartment("in");
+        co2ex = new BioCompartment("ex");
+        bn2.add(c2,c2ex,tc2,co2in,co2ex);
+        bn2.affectToCompartment(co2in,c2);
+        bn2.affectToCompartment(co2ex,c2ex);
+        bn2.affectLeft(tc2,1.0,co2in,c2);
+        bn2.affectRight(tc2,1.0,co2ex,c2ex);
+        d3ex = new BioMetabolite("d_ex","d");
+        d3 = new BioMetabolite("d_in","d");
+        f3 = new BioMetabolite("f_in","f");
+        r3 = new BioReaction("r2");
+        td3 = new BioReaction("td");
+        co3in = new BioCompartment("in");
+        co3ex = new BioCompartment("ex");
+        bn3.add(d3ex,d3,f3,r3,td3,co3in,co3ex);
+        bn3.affectToCompartment(co3in,d3,f3);
+        bn3.affectToCompartment(co3ex,d3ex);
+        bn3.affectLeft(td3,1.0,co3in,d3);
+        bn3.affectRight(td3,1.0,co3ex,d3ex);
+        bn3.affectLeft(r3,1.0,co3in,d3);
+        bn3.affectRight(r3,1.0,co3in,f3);
+        d4 = new BioMetabolite("d_in","d");
+        d4ex = new BioMetabolite("d_ex","d");
+        td4 = new BioReaction("td");
+        co4in = new BioCompartment("in");
+        co4ex = new BioCompartment("ex");
+        bn4.add(d4,d4ex,td4,co4in,co4ex);
+        bn4.affectToCompartment(co4in,d4);
+        bn4.affectToCompartment(co4ex,d4ex);
+        bn4.affectLeft(td4,1.0,co4in,d4);
+        bn4.affectRight(td4,1.0,co4ex,d4ex);
+    }
+
+    @Test
+    public void testCompoundsFuse(){
+        BioNetwork meta = initFuse();
+        assertEquals(14,meta.getMetabolitesView().size());
+        assertNotNull(meta.getMetabolite("model1_a_in"));
+        assertNotNull(meta.getMetabolite("model1_c_in"));
+        assertNotNull(meta.getMetabolite("model1_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_in"));
+
+        assertNotNull(meta.getMetabolite("pool_medium_c_ex"));
+        assertNotNull(meta.getMetabolite("pool_medium_d_ex"));
+        assertNotNull(meta.getMetabolite("pool_medium2_d_ex"));
+    }
+
+    @Test
+    public void testReactionsFuse() {
+        BioNetwork meta = initFuse();
+        assertEquals(10, meta.getReactionsView().size());
+        assertNotNull(meta.getReaction("poolReaction_model2_c_ex"));
+        assertNotNull(meta.getReaction("poolReaction_model1_c_ex"));
+        assertNotNull(meta.getReaction("poolReaction_model3_d_ex"));
+    }
+
+    @Test
+    public void testCompartmentFuse(){
+        BioNetwork meta = initFuse();
+        assertEquals(6,meta.getCompartmentsView().size());
+    }
+
+    @Test
+    public void testCompoundsFuseIncremental(){
+        BioNetwork meta = initFuseIncremental();
+        assertEquals(14,meta.getMetabolitesView().size());
+        assertNotNull(meta.getMetabolite("model1_a_in"));
+        assertNotNull(meta.getMetabolite("model1_c_in"));
+        assertNotNull(meta.getMetabolite("model1_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_in"));
+
+        Set<String> metabolites = meta.getMetabolitesView().stream().map(x -> x.getId()).collect(Collectors.toSet());
+        assertTrue(metabolites.contains("pool_medium_model1_c_ex")||metabolites.contains("pool_medium_model2_c_ex"));
+        assertNotNull(meta.getMetabolite("pool_medium_d_ex"));
+        assertNotNull(meta.getMetabolite("pool_medium2_d_ex"));
+    }
+
+    @Test
+    public void testReactionsFuseIncremental() {
+        BioNetwork meta = initFuseIncremental();
+        assertEquals(10, meta.getReactionsView().size());
+        assertNotNull(meta.getReaction("poolReaction_model2_c_ex"));
+        assertNotNull(meta.getReaction("poolReaction_model1_c_ex"));
+        assertNotNull(meta.getReaction("poolReaction_model3_d_ex"));
+    }
+
+    @Test
+    public void testCompartmentFuseIncremental(){
+        BioNetwork meta = initFuseIncremental();
+        assertEquals(6,meta.getCompartmentsView().size());
+    }
+
+
+    @Test
+    public void testCompoundsBump(){
+        BioNetwork meta = initBump();
+        assertEquals(19,meta.getMetabolitesView().size());
+        assertNotNull(meta.getMetabolite("model1_a_in"));
+        assertNotNull(meta.getMetabolite("model1_c_in"));
+        assertNotNull(meta.getMetabolite("model1_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_ex"));
+        assertNotNull(meta.getMetabolite("model2_c_in"));
+
+        assertNotNull(meta.getMetabolite("pool_medium_c_ex_medium"));
+        assertNotNull(meta.getMetabolite("pool_medium_d_ex_medium"));
+        assertNotNull(meta.getMetabolite("pool_medium2_d_ex_medium2"));
+    }
+
+    @Test
+    public void testReactionsBump() {
+        BioNetwork meta = initBump();
+        assertEquals(16, meta.getReactionsView().size());
+        assertNotNull(meta.getReaction("poolReaction_model2_c_ex_medium"));
+        assertNotNull(meta.getReaction("poolReaction_model1_c_ex_medium"));
+        assertNotNull(meta.getReaction("poolReaction_model3_d_ex_medium"));
+        assertNotNull(meta.getReaction("transport_model1_c_ex_to_medium"));
+        assertNotNull(meta.getReaction("poolReaction_model4_d_ex_medium2"));
+        assertNotNull(meta.getReaction("poolReaction_model4_d_ex_medium"));
+        assertNotNull(meta.getReaction("transport_model4_d_ex_to_medium2"));
+        assertNotNull(meta.getReaction("transport_model4_d_ex_to_medium"));
+    }
+
+    @Test
+    public void testCompartmentBump(){
+        BioNetwork meta = initBump();
+        assertEquals(10,meta.getCompartmentsView().size());
+    }
+}
diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/GenerateGalaxyFiles.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/GenerateGalaxyFiles.java
index ecd9317c2d8a71fd5e8c568ae9af66a7e2606d3a..1d26274c58bfe78f9e89f7f5162885c7b4aff488 100644
--- a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/GenerateGalaxyFiles.java
+++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/GenerateGalaxyFiles.java
@@ -57,7 +57,6 @@ import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 public class GenerateGalaxyFiles extends AbstractMet4jApplication {
diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/reconstruction/CreateMetaNetwork.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/reconstruction/CreateMetaNetwork.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0ef3029a8ec4f2e421a4fee66a60a0e3f18d518
--- /dev/null
+++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/reconstruction/CreateMetaNetwork.java
@@ -0,0 +1,204 @@
+package fr.inrae.toulouse.metexplore.met4j_toolbox.reconstruction;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.*;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork.CommunityNetworkBuilder;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.multinetwork.PrefixedMetaEntityFactory;
+import fr.inrae.toulouse.metexplore.met4j_io.annotations.network.NetworkAttributes;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.JsbmlReader;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.Met4jSbmlReaderException;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.plugin.*;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.units.BioUnitDefinition;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.writer.JsbmlWriter;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.writer.Met4jSbmlWriterException;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.AbstractMet4jApplication;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumFormats;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumParameterTypes;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.Format;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.ParameterType;
+import org.kohsuke.args4j.Option;
+
+import java.util.*;
+import java.util.function.Function;
+
+import static fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumFormats.Sbml;
+import static fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumParameterTypes.InputFile;
+import static fr.inrae.toulouse.metexplore.met4j_toolbox.reconstruction.CreateMetaNetwork.strategy.by_name;
+
+public class CreateMetaNetwork extends AbstractMet4jApplication {
+
+    //arguments
+    @Format(name = Sbml)
+    @ParameterType(name = InputFile)
+    @Option(name = "-n1", aliases = {"--network1"},  usage = "input SBML file: path to first network, in sbml format.", required = true)
+    public String sbml1FilePath;
+    @ParameterType(name = InputFile)
+    @Option(name = "-n2", aliases = {"--network2"},  usage = "input SBML file: path to second network, in sbml format.", required = true)
+    public String sbml2FilePath;
+
+    @Option(name = "-n1ex", aliases = {"--external1"},  usage = "external compartment identifier in first network.", required = true)
+    public String external1;
+    @Option(name = "-n2ex", aliases = {"--external2"},  usage = "external compartment identifier in second network.", required = true)
+    public String external2;
+
+    @Option(name = "-n1px", aliases = {"--n1prefix"},  usage = "prefix that will be added to first network's entities identifiers", required = false)
+    public String n1prefix = "Net1_";
+    @Option(name = "-n2px", aliases = {"--n2prefix"},   usage = "prefix that will be added to second network's entities identifiers", required = false)
+    public String n2prefix = "Net2_";
+
+    @Option(name = "-k", aliases = {"--keepCompartment"}, usage = "keep the original external compartments in the meta-network, otherwise, they will be fused into the new shared external compartment", required = false)
+    public boolean keepCompartment = false;
+
+    @Option(name = "-n1meta", aliases = {"--firstAsMeta"}, usage = "Treat first network as meta-network, allowing more than two sub-models with iterative fusions. This will overwrite shared compartment and pool compounds (which must follow the \"pool_\" prefix convention) and will ignore --n1prefix argument", required = false)
+    public boolean firstIsMeta = false;
+
+    enum strategy {by_metanetx, by_name, by_id}
+    @Option(name = "-mc", aliases = {"--mergingCriterion"}, usage = "field used to identify the same metabolites across the two different networks. " +
+            "\"by_name\"/\"by_id\" can be used if names/identifiers are consistent and unambiguous across source models, \"by_metanetx\" can be used if models contains MetaNetX identifiers in annotation field using standard miriam format.")
+    public CreateMetaNetwork.strategy mergingCriterion = by_name;
+
+    @ParameterType(name = EnumParameterTypes.OutputFile)
+    @Format(name = EnumFormats.Sbml)
+    @Option(name = "-o", usage = "output meta-network SBML file", required = true)
+    public String outputPath = null;
+
+    public static void main(String[] args) throws Met4jSbmlWriterException {
+        CreateMetaNetwork app = new CreateMetaNetwork();
+        app.parseArguments(args);
+        app.run();
+    }
+
+    public void run() throws Met4jSbmlWriterException {
+
+        if(Objects.equals(this.n1prefix, this.n2prefix)){
+            System.err.println("Error: prefixes must be different");
+            System.exit(1);
+        }
+
+        //import networks
+        System.out.print("Importing network 1...");
+        JsbmlReader reader = new JsbmlReader(this.sbml1FilePath);
+        ArrayList<PackageParser> pkgs = new ArrayList<>(Arrays.asList(
+                new NotesParser(false), new AnnotationParser(true), new FBCParser(), new GroupPathwayParser()));
+
+        BioNetwork network1 = null;
+        try {
+            network1 = reader.read(pkgs);
+        } catch (Met4jSbmlReaderException e) {
+            System.err.println("Error while reading the first SBML file");
+            System.err.println(e.getMessage());
+            System.exit(1);
+        }
+        BioCompartment co1ex = network1.getCompartment(external1);
+        if(co1ex==null){
+            System.err.println("Error: external compartment " + external1 + " not found in network 1");
+            System.exit(1);
+        }
+        if(firstIsMeta){
+            n1prefix = "";
+            BioMetabolite[] pool1 = network1.getCompartment(external1)
+                    .getComponentsView().stream()
+                    .filter(e -> e instanceof BioMetabolite)
+                    .filter(m -> m.getId().startsWith("pool_"))
+                    .toArray(BioMetabolite[]::new);
+            for(BioMetabolite pool : pool1){
+                network1.removeOnCascade(network1.getReactionsFromMetabolite(pool));
+            }
+            network1.removeOnCascade(pool1);
+        }
+        System.out.println(" Done.");
+
+
+        System.out.print("Importing network 2...");
+        reader = new JsbmlReader(this.sbml2FilePath);
+        BioNetwork network2 = null;
+        try {
+            network2 = reader.read(pkgs);
+        } catch (Met4jSbmlReaderException e) {
+            System.err.println("Error while reading the second SBML file");
+            System.err.println(e.getMessage());
+            System.exit(1);
+        }
+        BioCompartment co2ex = network2.getCompartment(external2);
+        if(co2ex==null){
+            System.err.println("Error: external compartment " + external2 + " not found in network 2");
+            System.exit(1);
+        }
+        System.out.println(" Done.");
+
+        System.out.print("Creating meta-network...");
+        //setup
+        BioCompartment medium=new BioCompartment("medium"); medium.setName("medium");
+        HashMap<BioNetwork,String> alias = new HashMap<>();
+        alias.put(network1,n1prefix);alias.put(network2,n2prefix);
+        CommunityNetworkBuilder builder = initMetaNetworkBuilder(medium, alias);
+        builder.add(network1);
+        builder.add(network2);
+
+        //build meta-network
+        if(!keepCompartment){
+            builder.fuseCompartmentIntoSharedCompartment(network1,co1ex,medium);
+            builder.fuseCompartmentIntoSharedCompartment(network2,co2ex,medium);
+        }else{
+            if(firstIsMeta) {
+                builder.fuseCompartmentIntoSharedCompartment(network1, co1ex, medium);
+            }else{
+                builder.bumpCompartmentIntoSharedCompartment(network1, co1ex, medium);
+            }
+            builder.bumpCompartmentIntoSharedCompartment(network2,co2ex,medium);
+        }
+        BioNetwork metaNetwork = builder.build();
+        System.out.println(" Done.");
+
+        //export the meta-network
+        System.out.print("Exporting MetaNetwork...");
+        NetworkAttributes.addUnitDefinition(metaNetwork, new BioUnitDefinition());
+        new JsbmlWriter(outputPath,metaNetwork).write();
+        System.out.println(" Done.");
+
+    }
+
+    private CommunityNetworkBuilder initMetaNetworkBuilder(BioCompartment medium, HashMap<BioNetwork, String> alias) {
+        CommunityNetworkBuilder builder = new CommunityNetworkBuilder(medium);
+        PrefixedMetaEntityFactory factory = new PrefixedMetaEntityFactory(alias,"pool_");
+        factory.addCompSuffix = (id, comp) -> id+"_"+comp.getId();
+        builder.setEntityFactory(factory);
+        Function<BioMetabolite, String> getSharedIdFunction;
+        switch (mergingCriterion) {
+            case by_metanetx:
+                getSharedIdFunction = x -> {
+                    try {
+                        BioRef r = x.getRefs("metanetx.chemical").iterator().next();
+                        return r.getId();
+                    } catch (NoSuchElementException | NullPointerException e) {
+                        return "unknown_"+UUID.randomUUID();
+                    }
+                };
+                break;
+            case by_id:
+                getSharedIdFunction = BioEntity::getId;
+                break;
+            case by_name:
+                getSharedIdFunction = BioEntity::getName;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid merging criterion: " + mergingCriterion);
+        }
+        builder.setGetSharedIdFunction(getSharedIdFunction);
+        return builder;
+    }
+
+    @Override
+    public String getLabel() {return this.getClass().getSimpleName();}
+
+    @Override
+    public String getShortDescription() {
+        return "Create a Meta-Network from two sub-networks in SBML format.";
+    }
+
+    @Override
+    public String getLongDescription() {
+        return "Create a Meta-Network from two sub-networks in SBML format. A meta-network is a single model which contains several sub-networks that remains individualized within" +
+                "the meta-network (as opposed to models fusion), but which can share some of their components with " +
+                "other sub-networks through a shared \"medium\" compartment.";
+    }
+}