diff --git a/pom.xml b/pom.xml
index f236b568..f5395f88 100644
--- a/pom.xml
+++ b/pom.xml
@@ -250,6 +250,10 @@
tools.jackson.dataformat
jackson-dataformat-toml
+
+ tools.jackson.dataformat
+ jackson-dataformat-yaml
+
diff --git a/src/main/java/land/oras/utils/YamlUtils.java b/src/main/java/land/oras/utils/YamlUtils.java
new file mode 100644
index 00000000..20aa7075
--- /dev/null
+++ b/src/main/java/land/oras/utils/YamlUtils.java
@@ -0,0 +1,99 @@
+/*-
+ * =LICENSE=
+ * ORAS Java SDK
+ * ===
+ * Copyright (C) 2024 - 2025 ORAS
+ * ===
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =LICENSEEND=
+ */
+
+package land.oras.utils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import land.oras.exception.OrasException;
+import org.jspecify.annotations.NullMarked;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.dataformat.yaml.YAMLMapper;
+
+/**
+ * Utility class for YAML operations.
+ * Use Jackson 3 internally for YAML operations
+ */
+@NullMarked
+public final class YamlUtils {
+
+ /**
+ * TOML mapper instance
+ */
+ private static final ObjectMapper yamlMapper;
+
+ /**
+ * Utils class
+ */
+ private YamlUtils() {
+ // Hide constructor
+ }
+
+ static {
+ yamlMapper = YAMLMapper.builder().build();
+ }
+
+ /**
+ * Convert an object to a YAML string
+ * @param object The object to convert
+ * @return The YAML string
+ */
+ public static String toYaml(Object object) {
+ try {
+ return yamlMapper.writeValueAsString(object);
+ } catch (JacksonException e) {
+ throw new OrasException("Unable to convert object to YAML string", e);
+ }
+ }
+
+ /**
+ * Convert a YAML string to an object
+ * @param yaml The YAML string
+ * @param clazz The class of the object
+ * @param The type of the object
+ * @return The object
+ */
+ public static T fromYaml(String yaml, Class clazz) {
+ try {
+ return yamlMapper.readValue(yaml, clazz);
+ } catch (JacksonException e) {
+ throw new OrasException("Unable to parse YAML string", e);
+ }
+ }
+
+ /**
+ * Read a YAML file and convert its contents to an object.
+ * The file at the given {@code path} is read as UTF-8 YAML and deserialized into the specified type.
+ * @param path The path to the YAML file
+ * @param clazz The class of the object
+ * @param The type of the object
+ * @return The object deserialized from the YAML file
+ */
+ public static T fromYaml(Path path, Class clazz) {
+ try {
+ return yamlMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz);
+ } catch (IOException | JacksonException e) {
+ throw new OrasException("Unable to read YAML from file", e);
+ }
+ }
+}
diff --git a/src/test/java/land/oras/utils/YamlUtilsTest.java b/src/test/java/land/oras/utils/YamlUtilsTest.java
new file mode 100644
index 00000000..571eb6bd
--- /dev/null
+++ b/src/test/java/land/oras/utils/YamlUtilsTest.java
@@ -0,0 +1,107 @@
+/*-
+ * =LICENSE=
+ * ORAS Java SDK
+ * ===
+ * Copyright (C) 2024 - 2025 ORAS
+ * ===
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =LICENSEEND=
+ */
+
+package land.oras.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import land.oras.exception.OrasException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.CleanupMode;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+@Execution(ExecutionMode.CONCURRENT)
+class YamlUtilsTest {
+
+ /**
+ * Temporary dir
+ */
+ @TempDir(cleanup = CleanupMode.ON_SUCCESS)
+ private static Path dir;
+
+ @Test
+ void failToParseYamlString() {
+ assertThrows(OrasException.class, () -> YamlUtils.fromYaml("not a yaml: too, bar: test", Object.class));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void shouldParseYaml() {
+ String yamlMap =
+ """
+ ---
+ key1: "value1"
+ key2: "value2"
+ """;
+ Map map = YamlUtils.fromYaml(yamlMap, Map.class);
+ assertNotNull(map);
+ assertEquals(2, map.size());
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void shouldParseYamlFile() throws IOException {
+ String yamlMap =
+ """
+ ---
+ key1: "value1"
+ key2: "value2"
+ """;
+ Path yamlFile = dir.resolve("test.yaml");
+ Files.writeString(yamlFile, yamlMap);
+ Map map = YamlUtils.fromYaml(yamlFile, Map.class);
+ assertNotNull(map);
+ assertEquals(2, map.size());
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void shouldFailToParseYamlFile() {
+ assertThrows(OrasException.class, () -> YamlUtils.fromYaml(Path.of("foo.yaml"), Map.class));
+ }
+
+ @Test
+ void shouldConvertToYaml() {
+ Map map = new LinkedHashMap<>();
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ String yaml = YamlUtils.toYaml(map);
+ assertNotNull(yaml);
+ String expected = """
+ ---
+ key1: "value1"
+ key2: "value2"
+ """;
+ assertEquals(expected, yaml);
+ }
+}