diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
index 3a31151f..21980ad0 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunctionFactory.java
@@ -12,6 +12,7 @@ import com.jayway.jsonpath.internal.function.sequence.First;
import com.jayway.jsonpath.internal.function.sequence.Index;
import com.jayway.jsonpath.internal.function.sequence.Last;
import com.jayway.jsonpath.internal.function.text.Concatenate;
+import com.jayway.jsonpath.internal.function.text.Join;
import com.jayway.jsonpath.internal.function.text.Length;
import java.util.Collections;
@@ -43,6 +44,7 @@ public class PathFunctionFactory {
// Text Functions
map.put("concat", Concatenate.class);
+ map.put("join", Join.class);
// JSON Entity Functions
map.put(Length.TOKEN_NAME, Length.class);
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java
new file mode 100644
index 00000000..19e7b5fc
--- /dev/null
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/text/Join.java
@@ -0,0 +1,166 @@
+package com.jayway.jsonpath.internal.function.text;
+
+import com.jayway.jsonpath.internal.EvaluationContext;
+import com.jayway.jsonpath.internal.PathRef;
+import com.jayway.jsonpath.internal.function.ParamType;
+import com.jayway.jsonpath.internal.function.Parameter;
+import com.jayway.jsonpath.internal.function.PathFunction;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * join values in an array, supply two grammars:
+ *
+ * 1. [].join([delimiter],[..path])
+ * 2. $.join([delimiter],[..path])
+ */
+public class Join implements PathFunction {
+ @Override
+ public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) {
+ String delimiter = this.getDelimiterFromParameters(parameters);
+ Collection results = Collections.emptyList();
+
+ // [].join([..path])
+ if (ctx.configuration().jsonProvider().isArray(model)) {
+ results = this.joinByArrayModel(model, ctx, parameters);
+ }
+
+ // $.join([delimiter],[..path])
+ if (null == results || results.size() == 0) {
+ results = this.joinByParams(ctx, parameters);
+ }
+
+ if (null == results || results.size() == 0) {
+ return "";
+ }
+ return String.join(delimiter, results);
+ }
+
+
+ /**
+ * get results by model
+ *
+ * @param model
+ * @param ctx
+ * @param parameters
+ * @return
+ */
+ protected Collection joinByArrayModel(Object model, EvaluationContext ctx, List parameters) {
+ Collection resultList = new ArrayList<>();
+ List pathParams = Optional.ofNullable(parameters).orElseGet(Collections::emptyList)
+ .stream().filter(item -> ParamType.PATH.equals(item.getType())).collect(Collectors.toList());
+
+ if (pathParams.size() == 0) {
+ return this.arrayIterableToList(ctx, model, item -> null);
+ }
+
+ for (Parameter pathParam : pathParams) {
+ List list = this.arrayIterableToList(ctx, model, item -> {
+ Object value = pathParam.getPath().evaluate(item, model, ctx.configuration()).getValue();
+ return this.arrayIterableToList(ctx, value, obj -> null);
+ });
+ if (null == list || list.size() == 0) {
+ continue;
+ }
+ resultList.addAll(list);
+ }
+ return resultList;
+ }
+
+
+ /**
+ * get results by params
+ *
+ * @param ctx
+ * @param parameters
+ * @return
+ */
+ protected Collection joinByParams(EvaluationContext ctx, List parameters) {
+ if (null == parameters || parameters.size() == 0) {
+ return null;
+ }
+ List notJsonParams = parameters.stream().filter(item -> ParamType.PATH.equals(item.getType())).collect(Collectors.toList());
+ if (notJsonParams.size() == 0) {
+ return parameters.stream().map(Parameter::getValue).filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList());
+ }
+ List list = Parameter.toList(String.class, ctx, notJsonParams);
+ return list.size() > 0 ? list : null;
+ }
+
+ /**
+ * get delimiter
+ *
+ * @param parameters
+ * @return
+ */
+ protected String getDelimiterFromParameters(List parameters) {
+ if (null != parameters && parameters.size() >= 1) {
+ Parameter parameter = parameters.get(0);
+ if (ParamType.JSON.equals(parameter.getType())) {
+ return parameter.getValue().toString();
+ }
+ }
+ return ",";
+ }
+
+
+ /**
+ * literal quantity type
+ *
+ * @param obj
+ * @return
+ */
+ protected boolean simpleType(Object obj) {
+ if (null == obj) {
+ return false;
+ }
+ return obj instanceof Number || obj instanceof CharSequence ||
+ obj instanceof Boolean || obj instanceof Character;
+ }
+
+
+ /**
+ * iterable
+ *
+ * @param ctx
+ * @param model
+ * @param itemIsNotSimpleFunc
+ */
+ protected List arrayIterableToList(EvaluationContext ctx, Object model, Function