diff --git a/.vscode/launch.json b/.vscode/launch.json
index 57261f9f..7e154613 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -18,6 +18,7 @@
     {
       "type": "java",
       "name": "Attach to Plugin",
+      "projectName": "com.microsoft.jdtls.ext.core",
       "request": "attach",
       "hostName": "localhost",
       "port": 1044
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..9917e21e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+# How to Contribute
+
+We greatly appreciate contributions to the vscode-java-dependency project. Your efforts help us maintain and improve this extension. To ensure a smooth contribution process, please follow these guidelines.
+
+## Prerequisites
+- [JDK](https://www.oracle.com/java/technologies/downloads/?er=221886)
+- [Node.JS](https://nodejs.org/en/)
+- [VSCode](https://code.visualstudio.com/)
+
+## Build and Run
+
+To set up the vscode-java-dependency project, follow these steps:
+
+1. **Build the Server JAR**:
+   - The server JAR (Java application) is located in the [jdtls.ext](./jdtls.ext) directory.
+   - Run the following command to build the server:
+     ```shell
+     npm run build-server
+     ```
+
+2. **Install Dependencies**:
+   - Execute the following command to install the necessary dependencies:
+     ```shell
+     npm install
+     ```
+
+3. **Run/Debug the Extension**:
+   - Open the "Run and Debug" view in Visual Studio Code.
+   - Run the "Run Extension" task.
+
+4. **Attach to Plugin[Debug Java]**:
+   - Prerequisite: Ensure that the extension is activated, meaning the Java process is already launched. This is required for the task to run properly.
+   - Open the "Run and Debug" view in Visual Studio Code.
+   - Run the "Attach to Plugin" task.
+   - Note: This task is required only if you want to debug Java code [jdtls.ext](./jdtls.ext). It requires the [vscode-pde](https://marketplace.visualstudio.com/items?itemName=yaozheng.vscode-pde) extension to be installed.
+
+Thank you for your contributions and support!
\ No newline at end of file
diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java
index 1bb2e99b..cada8754 100644
--- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java
+++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/PackageCommand.java
@@ -14,6 +14,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -78,7 +79,7 @@ public class PackageCommand {
     private static final Map<NodeKind, BiFunction<PackageParams, IProgressMonitor, List<PackageNode>>> commands;
 
     static {
-        commands = new HashMap<>();
+        commands = new EnumMap<>(NodeKind.class);
         commands.put(NodeKind.PROJECT, PackageCommand::getProjectChildren);
         commands.put(NodeKind.CONTAINER, PackageCommand::getContainerChildren);
         commands.put(NodeKind.PACKAGEROOT, PackageCommand::getPackageRootChildren);
diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java
index 7f146d8d..92fa4cf7 100644
--- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java
+++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/model/PackageNode.java
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.commons.lang3.StringUtils;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
@@ -273,11 +274,32 @@ public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentR
             for (IClasspathAttribute attribute : resolvedClasspathEntry.getExtraAttributes()) {
                 node.setMetaDataValue(attribute.getName(), attribute.getValue());
             }
+
+            String computedDisplayName = computeDisplayName(node);
+            if (StringUtils.isNotBlank(computedDisplayName)) {
+                node.setDisplayName(computedDisplayName);
+            }
         }
 
         return node;
     }
 
+    private static String computeDisplayName(PackageRootNode node) {
+        if (node.getMetaData() == null || node.getMetaData().isEmpty()) {
+            return node.getName();
+        }
+
+        String version = (String) node.getMetaData().get("maven.version");
+        String groupId = (String) node.getMetaData().get("maven.groupId");
+        String artifactId = (String) node.getMetaData().get("maven.artifactId");
+
+        if (StringUtils.isBlank(version) || StringUtils.isBlank(groupId) || StringUtils.isBlank(artifactId)) {
+            return node.getName();
+        }
+
+        return groupId + ":" + artifactId + ":" + version;
+    }
+
     /**
      * Get the correspond node of classpath, it may be container or a package root.
      *
diff --git a/src/views/PrimaryTypeNode.ts b/src/views/PrimaryTypeNode.ts
index 8ee95223..0d80bf8b 100644
--- a/src/views/PrimaryTypeNode.ts
+++ b/src/views/PrimaryTypeNode.ts
@@ -34,6 +34,10 @@ export class PrimaryTypeNode extends DataNode {
         return "";
     }
 
+    public getLabel(): string {
+        return this._nodeData.displayName ?? this._nodeData.name;
+    }
+
     protected async loadData(): Promise<SymbolInformation[] | DocumentSymbol[] | undefined> {
         if (!this.hasChildren() || !this.nodeData.uri) {
             return undefined;
diff --git a/src/views/containerNode.ts b/src/views/containerNode.ts
index 0245514c..a8cbd6c7 100644
--- a/src/views/containerNode.ts
+++ b/src/views/containerNode.ts
@@ -15,23 +15,36 @@ export class ContainerNode extends DataNode {
         super(nodeData, parent);
     }
 
+    private _containerType: ContainerType;
+
     public get projectBasePath() {
         return this._project.uri && Uri.parse(this._project.uri).fsPath;
     }
 
-    public getContainerType(): string {
+    public getContainerType(): ContainerType {
+        if (this._containerType) {
+            return this._containerType;
+        }
+
         const containerPath: string = this._nodeData.path || "";
         if (containerPath.startsWith(ContainerPath.JRE)) {
-            return ContainerType.JRE;
+            this._containerType = ContainerType.JRE;
         } else if (containerPath.startsWith(ContainerPath.Maven)) {
-            return ContainerType.Maven;
+            this._containerType = ContainerType.Maven;
         } else if (containerPath.startsWith(ContainerPath.Gradle)) {
-            return ContainerType.Gradle;
+            this._containerType = ContainerType.Gradle;
         } else if (containerPath.startsWith(ContainerPath.ReferencedLibrary) && this._project.isUnmanagedFolder()) {
             // currently, we only support editing referenced libraries in unmanaged folders
-            return ContainerType.ReferencedLibrary;
+            this._containerType = ContainerType.ReferencedLibrary;
+        } else {
+            this._containerType = ContainerType.Unknown;
         }
-        return ContainerType.Unknown;
+
+        return this._containerType;
+    }
+
+    public isMavenType(): boolean {
+        return this._containerType === ContainerType.Maven;
     }
 
     protected async loadData(): Promise<INodeData[]> {
diff --git a/src/views/dataNode.ts b/src/views/dataNode.ts
index 21be07a7..200abf13 100644
--- a/src/views/dataNode.ts
+++ b/src/views/dataNode.ts
@@ -42,6 +42,10 @@ export abstract class DataNode extends ExplorerNode {
         return item;
     }
 
+    public getDisplayName(): string {
+        return this._nodeData.displayName || this._nodeData.name;
+    }
+
     public get nodeData(): INodeData {
         return this._nodeData;
     }
diff --git a/src/views/dependencyDataProvider.ts b/src/views/dependencyDataProvider.ts
index dd0bd05b..12001be2 100644
--- a/src/views/dependencyDataProvider.ts
+++ b/src/views/dependencyDataProvider.ts
@@ -7,7 +7,7 @@ import {
     RelativePattern, TreeDataProvider, TreeItem, Uri, window, workspace,
 } from "vscode";
 import { instrumentOperationAsVsCodeCommand, sendError } from "vscode-extension-telemetry-wrapper";
-import { contextManager } from "../../extension.bundle";
+import { ContainerNode, contextManager } from "../../extension.bundle";
 import { Commands } from "../commands";
 import { Context } from "../constants";
 import { appendOutput, executeExportJarTask } from "../tasks/buildArtifact/BuildArtifactTaskProvider";
@@ -124,6 +124,14 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
         const children = (!this._rootItems || !element) ?
             await this.getRootNodes() : await element.getChildren();
 
+        if (children && element instanceof ContainerNode) {
+            if (element.isMavenType()) {
+                children.sort((a, b) => {
+                    return a.getDisplayName().localeCompare(b.getDisplayName());
+                });
+            }
+        }
+
         explorerNodeCache.saveNodes(children || []);
         return children;
     }
diff --git a/src/views/documentSymbolNode.ts b/src/views/documentSymbolNode.ts
index a6ead753..7552116e 100644
--- a/src/views/documentSymbolNode.ts
+++ b/src/views/documentSymbolNode.ts
@@ -28,6 +28,10 @@ export class DocumentSymbolNode extends ExplorerNode {
         super(parent);
     }
 
+    public getDisplayName(): string {
+        return this.symbolInfo.name;
+    }
+
     public getChildren(): ExplorerNode[] | Promise<ExplorerNode[]> {
         const res: ExplorerNode[] = [];
         if (this.symbolInfo?.children?.length) {
@@ -39,7 +43,7 @@ export class DocumentSymbolNode extends ExplorerNode {
     }
 
     public getTreeItem(): TreeItem | Promise<TreeItem> {
-        const item = new TreeItem(this.symbolInfo.name,
+        const item = new TreeItem(this.getDisplayName(),
             this.symbolInfo?.children?.length ? TreeItemCollapsibleState.Collapsed
                 : TreeItemCollapsibleState.None);
         item.iconPath = this.iconPath;
diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts
index c5c29092..1dcac373 100644
--- a/src/views/explorerNode.ts
+++ b/src/views/explorerNode.ts
@@ -33,4 +33,6 @@ export abstract class ExplorerNode {
     public abstract getTreeItem(): TreeItem | Promise<TreeItem>;
 
     public abstract computeContextValue(): string | undefined;
+
+    public abstract getDisplayName(): string;
 }
diff --git a/test/gradle-suite/projectView.test.ts b/test/gradle-suite/projectView.test.ts
index ec5a045d..4a4e6776 100644
--- a/test/gradle-suite/projectView.test.ts
+++ b/test/gradle-suite/projectView.test.ts
@@ -3,13 +3,17 @@
 
 import * as assert from "assert";
 import { ContainerNode, contextManager, DataNode, DependencyExplorer,
+    languageServerApiManager,
     PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
 import { fsPath, setupTestEnv, Uris } from "../shared";
 
 // tslint:disable: only-arrow-functions
 suite("Gradle Project View Tests", () => {
 
-    suiteSetup(setupTestEnv);
+    suiteSetup(async () => {
+        await setupTestEnv();
+        await languageServerApiManager.ready();
+    });
 
     test("Can node render correctly", async function() {
         const explorer = DependencyExplorer.getInstance(contextManager.context);
diff --git a/test/invisible-suite/projectView.test.ts b/test/invisible-suite/projectView.test.ts
index c1488ce7..61e91d01 100644
--- a/test/invisible-suite/projectView.test.ts
+++ b/test/invisible-suite/projectView.test.ts
@@ -3,22 +3,21 @@
 
 import * as assert from "assert";
 import * as fse from "fs-extra";
-import { platform } from "os";
 import * as path from "path";
 import * as vscode from "vscode";
-import { Commands, contextManager, DependencyExplorer, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle";
+import { Commands, contextManager, DependencyExplorer, languageServerApiManager, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle";
 import { setupTestEnv } from "../shared";
 import { sleep } from "../util";
 
 // tslint:disable: only-arrow-functions
 suite("Invisible Project View Tests", () => {
 
-    suiteSetup(setupTestEnv);
+    suiteSetup(async () => {
+        await setupTestEnv();
+        await languageServerApiManager.ready();
+    });
 
     test("Can execute command java.project.refreshLibraries correctly", async function() {
-        if (platform() === "darwin") {
-            this.skip();
-        }
         const explorer = DependencyExplorer.getInstance(contextManager.context);
 
         let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
@@ -37,9 +36,6 @@ suite("Invisible Project View Tests", () => {
     });
 
     test("Can execute command java.project.removeLibrary correctly", async function() {
-        if (platform() === "darwin") {
-            this.skip();
-        }
         const explorer = DependencyExplorer.getInstance(contextManager.context);
 
         let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
diff --git a/test/maven-suite/projectView.test.ts b/test/maven-suite/projectView.test.ts
index f6863758..ec305edd 100644
--- a/test/maven-suite/projectView.test.ts
+++ b/test/maven-suite/projectView.test.ts
@@ -4,13 +4,16 @@
 import * as assert from "assert";
 import * as vscode from "vscode";
 import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode,
-    INodeData, Jdtls, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
+    INodeData, Jdtls, languageServerApiManager, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
 import { fsPath, printNodes, setupTestEnv, Uris } from "../shared";
 
 // tslint:disable: only-arrow-functions
 suite("Maven Project View Tests", () => {
 
-    suiteSetup(setupTestEnv);
+    suiteSetup(async () => {
+        await setupTestEnv();
+        await languageServerApiManager.ready();
+    });
 
     test("Can node render correctly in hierarchical view", async function() {
         await vscode.workspace.getConfiguration("java.dependency").update("packagePresentation", "hierarchical");
@@ -253,6 +256,19 @@ suite("Maven Project View Tests", () => {
         assert.equal(projectChildren.length, 4);
     });
 
+    test("Can maven dependency nodes display in correct groupId:artifactId:version format", async function() {
+        const explorer = DependencyExplorer.getInstance(contextManager.context);
+
+        const roots = await explorer.dataProvider.getChildren();
+        const projectNode = roots![0] as ProjectNode;
+        const projectChildren = await projectNode.getChildren();
+        const mavenDependency = projectChildren[3] as ContainerNode;
+        const mavenChildren = await mavenDependency.getChildren();
+
+        assert.equal(mavenChildren[0].getDisplayName(), "org.hamcrest:hamcrest-core:1.3");
+        assert.equal(mavenChildren[1].getDisplayName(), "junit:junit:4.13.1");
+    });
+
     teardown(async () => {
         // Restore default settings. Some tests might alter them and others depend on a specific setting.
         // Not resetting to the default settings will also show the file as changed in the source control view.