diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TermsRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TermsRequest.java
index 4b3c2cae1a5..7e488a8ec2a 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TermsRequest.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TermsRequest.java
@@ -20,6 +20,7 @@
 package org.elasticsearch.action.terms;
 
 import org.elasticsearch.ElasticSearchIllegalArgumentException;
+import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.support.broadcast.BroadcastOperationRequest;
 import org.elasticsearch.index.mapper.AllFieldMapper;
 import org.elasticsearch.util.io.stream.StreamInput;
@@ -141,6 +142,14 @@ public class TermsRequest extends BroadcastOperationRequest {
         super(indices, null);
     }
 
+    @Override public ActionRequestValidationException validate() {
+        ActionRequestValidationException validationException = super.validate();
+        if (fields == null || fields.length == 0) {
+            fields = DEFAULT_FIELDS;
+        }
+        return validationException;
+    }
+
     /**
      * The fields within each document which terms will be iterated over and returned with the
      * document frequencies.
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java
index d192b1a96ab..3427a93d229 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java
@@ -196,6 +196,18 @@ public class TransportTermsAction extends TransportBroadcastOperationAction<Term
                             if (term == null || indexFieldName != term.field()) { // StirngHelper.intern
                                 break;
                             }
+                            // convert to actual term text
+                            if (fieldMapper != null && fieldMapper.requiresStringToStringConversion()) {
+                                // valueAsString returns null indicating that this is not interesting
+                                term = term.createTerm(fieldMapper.valueAsString(term.text()));
+                                // if we need to break on this term enumeration, bail
+                                if (fieldMapper.shouldBreakTermEnumeration(term.text())) {
+                                    break;
+                                }
+                                if (term.text() == null) {
+                                    continue;
+                                }
+                            }
                             // does it match on the prefix?
                             if (request.prefix() != null && !term.text().startsWith(request.prefix())) {
                                 break;
@@ -240,6 +252,18 @@ public class TransportTermsAction extends TransportBroadcastOperationAction<Term
                             if (term == null || indexFieldName != term.field()) { // StirngHelper.intern
                                 break;
                             }
+                            // convert to actual term text
+                            if (fieldMapper != null && fieldMapper.requiresStringToStringConversion()) {
+                                // valueAsString returns null indicating that this is not interesting
+                                term = term.createTerm(fieldMapper.valueAsString(term.text()));
+                                // if we need to break on this term enumeration, bail
+                                if (fieldMapper.shouldBreakTermEnumeration(term.text())) {
+                                    break;
+                                }
+                                if (term.text() == null) {
+                                    continue;
+                                }
+                            }
                             // does it match on the prefix?
                             if (request.prefix() != null && !term.text().startsWith(request.prefix())) {
                                 break;
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/node/Node.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/node/Node.java
index dbb0bb9c558..678ac34445c 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/node/Node.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/node/Node.java
@@ -20,6 +20,7 @@
 package org.elasticsearch.cluster.node;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.lucene.util.StringHelper;
 import org.elasticsearch.util.io.stream.StreamInput;
 import org.elasticsearch.util.io.stream.StreamOutput;
 import org.elasticsearch.util.io.stream.Streamable;
@@ -36,7 +37,7 @@ public class Node implements Streamable, Serializable {
 
     public static final ImmutableList<Node> EMPTY_LIST = ImmutableList.of();
 
-    private String nodeName = "";
+    private String nodeName = StringHelper.intern("");
 
     private String nodeId;
 
@@ -52,12 +53,13 @@ public class Node implements Streamable, Serializable {
     }
 
     public Node(String nodeName, boolean dataNode, String nodeId, TransportAddress address) {
-        this.nodeName = nodeName;
-        this.dataNode = dataNode;
-        if (this.nodeName == null) {
-            this.nodeName = "";
+        if (nodeName == null) {
+            this.nodeName = StringHelper.intern("");
+        } else {
+            this.nodeName = StringHelper.intern(nodeName);
         }
-        this.nodeId = nodeId;
+        this.dataNode = dataNode;
+        this.nodeId = StringHelper.intern(nodeId);
         this.address = address;
     }
 
@@ -96,9 +98,9 @@ public class Node implements Streamable, Serializable {
     }
 
     @Override public void readFrom(StreamInput in) throws IOException {
-        nodeName = in.readUTF();
+        nodeName = StringHelper.intern(in.readUTF());
         dataNode = in.readBoolean();
-        nodeId = in.readUTF();
+        nodeId = StringHelper.intern(in.readUTF());
         address = TransportAddressSerializers.addressFromStream(in);
     }
 
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/Index.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/Index.java
index ee3223c9a01..ffa4cc18d66 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/Index.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/Index.java
@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index;
 
+import org.apache.lucene.util.StringHelper;
 import org.elasticsearch.util.concurrent.Immutable;
 import org.elasticsearch.util.io.stream.StreamInput;
 import org.elasticsearch.util.io.stream.StreamOutput;
@@ -40,7 +41,7 @@ public class Index implements Serializable, Streamable {
     }
 
     public Index(String name) {
-        this.name = name;
+        this.name = StringHelper.intern(name);
     }
 
     public String name() {
@@ -73,7 +74,7 @@ public class Index implements Serializable, Streamable {
     }
 
     @Override public void readFrom(StreamInput in) throws IOException {
-        name = in.readUTF();
+        name = StringHelper.intern(in.readUTF());
     }
 
     @Override public void writeTo(StreamOutput out) throws IOException {
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java
index 03b357afae9..de5ffffc1c0 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java
@@ -128,6 +128,25 @@ public interface FieldMapper<T> {
      */
     String valueAsString(Fieldable field);
 
+    /**
+     * Returns <tt>true</tt> if {@link #valueAsString(String)} is required to convert
+     * from text value to text value.
+     */
+    boolean requiresStringToStringConversion();
+
+    /**
+     * Converts from the internal/indexed (term) text to the actual string representation.
+     * Can return <tt>null</tt> indicating that this is "uninteresting" value (for example, with
+     * numbers). Useful for example when enumerating terms. See {@link #shouldBreakTermEnumeration(String)}.
+     */
+    String valueAsString(String text);
+
+    /**
+     * Return <tt>true</tt> if this term value indicates breaking out of term enumeration on this
+     * field. The term text passed is the one returned from {@link #valueAsString(String)}.
+     */
+    boolean shouldBreakTermEnumeration(String text);
+
     /**
      * Returns the indexed value.
      */
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonBoostFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonBoostFieldMapper.java
index ff4061f6909..89c113342a7 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonBoostFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonBoostFieldMapper.java
@@ -110,6 +110,14 @@ public class JsonBoostFieldMapper extends JsonNumberFieldMapper<Float> implement
         return NumericUtils.floatToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        final int shift = text.charAt(0) - NumericUtils.SHIFT_START_INT;
+        if (shift > 0 && shift <= 31) {
+            return null;
+        }
+        return Float.toString(NumericUtils.prefixCodedToFloat(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newFloatRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : Float.parseFloat(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java
index 0d2490285b6..e4186482f3b 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java
@@ -115,6 +115,14 @@ public class JsonDateFieldMapper extends JsonNumberFieldMapper<Long> {
         return NumericUtils.longToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        final int shift = text.charAt(0) - NumericUtils.SHIFT_START_LONG;
+        if (shift > 0 && shift <= 63) {
+            return null;
+        }
+        return dateTimeFormatter.printer().print(NumericUtils.prefixCodedToLong(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newLongRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : dateTimeFormatter.parser().parseMillis(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDoubleFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDoubleFieldMapper.java
index 554fe669ea4..544f13e06da 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDoubleFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDoubleFieldMapper.java
@@ -99,6 +99,14 @@ public class JsonDoubleFieldMapper extends JsonNumberFieldMapper<Double> {
         return NumericUtils.doubleToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        final int shift = text.charAt(0) - NumericUtils.SHIFT_START_LONG;
+        if (shift > 0 && shift <= 63) {
+            return null;
+        }
+        return Double.toString(NumericUtils.prefixCodedToDouble(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newDoubleRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : Double.parseDouble(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFieldMapper.java
index f85285db1bb..ab018930f8c 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFieldMapper.java
@@ -292,6 +292,27 @@ public abstract class JsonFieldMapper<T> implements FieldMapper<T>, JsonMapper {
         return valueAsString(field);
     }
 
+    /**
+     * Default base does not require stringToString conversion.
+     */
+    @Override public boolean requiresStringToStringConversion() {
+        return false;
+    }
+
+    /**
+     * Simply returns the same string.
+     */
+    @Override public String valueAsString(String text) {
+        return text;
+    }
+
+    /**
+     * Never break on this term enumeration value.
+     */
+    @Override public boolean shouldBreakTermEnumeration(String text) {
+        return false;
+    }
+
     @Override public String indexedValue(String value) {
         return value;
     }
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFloatFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFloatFieldMapper.java
index 865ba811295..1bee19bab74 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFloatFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonFloatFieldMapper.java
@@ -99,6 +99,14 @@ public class JsonFloatFieldMapper extends JsonNumberFieldMapper<Float> {
         return NumericUtils.floatToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        final int shift = text.charAt(0) - NumericUtils.SHIFT_START_INT;
+        if (shift > 0 && shift <= 31) {
+            return null;
+        }
+        return Float.toString(NumericUtils.prefixCodedToFloat(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newFloatRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : Float.parseFloat(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonIntegerFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonIntegerFieldMapper.java
index e06407d5546..12820c5454c 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonIntegerFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonIntegerFieldMapper.java
@@ -98,6 +98,10 @@ public class JsonIntegerFieldMapper extends JsonNumberFieldMapper<Integer> {
         return NumericUtils.intToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        return Integer.toString(NumericUtils.prefixCodedToInt(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newIntRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : Integer.parseInt(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonLongFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonLongFieldMapper.java
index dc3076873e0..37a826418fb 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonLongFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonLongFieldMapper.java
@@ -98,6 +98,14 @@ public class JsonLongFieldMapper extends JsonNumberFieldMapper<Long> {
         return NumericUtils.longToPrefixCoded(value);
     }
 
+    @Override public String valueAsString(String text) {
+        final int shift = text.charAt(0) - NumericUtils.SHIFT_START_LONG;
+        if (shift > 0 && shift <= 63) {
+            return null;
+        }
+        return Long.toString(NumericUtils.prefixCodedToLong(text));
+    }
+
     @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) {
         return NumericRangeQuery.newLongRange(names.indexName(), precisionStep,
                 lowerTerm == null ? null : Long.parseLong(lowerTerm),
diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonNumberFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonNumberFieldMapper.java
index c5fd87e1c6d..a2940067b5b 100644
--- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonNumberFieldMapper.java
+++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonNumberFieldMapper.java
@@ -129,6 +129,22 @@ public abstract class JsonNumberFieldMapper<T extends Number> extends JsonFieldM
         return value(field).toString();
     }
 
+    /**
+     * Numbers require string conversion.
+     */
+    @Override public boolean requiresStringToStringConversion() {
+        return true;
+    }
+
+    @Override public abstract String valueAsString(String text);
+
+    /**
+     * Breaks on this text if its <tt>null</tt>.
+     */
+    @Override public boolean shouldBreakTermEnumeration(String text) {
+        return text == null;
+    }
+
     @Override protected void doJsonBody(JsonBuilder builder) throws IOException {
         super.doJsonBody(builder);
         builder.field("precisionStep", precisionStep);