diff --git a/cspell.yml b/cspell.yml
index 36a6cf6e0c..9f6d2816ad 100644
--- a/cspell.yml
+++ b/cspell.yml
@@ -24,6 +24,8 @@ words:
   - graphiql
   - sublinks
   - instanceof
+  - uncoerce
+  - uncoerced
 
   # Different names used inside tests
   - Skywalker
diff --git a/src/execution/__tests__/variables-test.ts b/src/execution/__tests__/variables-test.ts
index 7ac91b20c2..c5db7e99fc 100644
--- a/src/execution/__tests__/variables-test.ts
+++ b/src/execution/__tests__/variables-test.ts
@@ -103,7 +103,6 @@ const TestType = new GraphQLObjectType({
     }),
     fieldWithNestedInputObject: fieldWithInputArg({
       type: TestNestedInputObject,
-      defaultValue: 'Hello World',
     }),
     list: fieldWithInputArg({ type: new GraphQLList(GraphQLString) }),
     nnList: fieldWithInputArg({
diff --git a/src/index.ts b/src/index.ts
index d9f5e8eda8..ab3a2598e9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -411,8 +411,6 @@ export {
   typeFromAST,
   /** Create a JavaScript value from a GraphQL language AST without a Type. */
   valueFromASTUntyped,
-  /** Create a GraphQL language AST from a JavaScript value. */
-  astFromValue,
   /** A helper to use within recursive-descent visitors which need to be aware of the GraphQL type system. */
   TypeInfo,
   visitWithTypeInfo,
diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts
index fd3a0617e2..210b5fac45 100644
--- a/src/type/__tests__/enumType-test.ts
+++ b/src/type/__tests__/enumType-test.ts
@@ -60,9 +60,7 @@ const QueryType = new GraphQLObjectType({
       args: {
         fromEnum: {
           type: ComplexEnum,
-          // Note: defaultValue is provided an *internal* representation for
-          // Enums, rather than the string name.
-          defaultValue: Complex1,
+          defaultValue: 'ONE',
         },
         provideGoodValue: { type: GraphQLBoolean },
         provideBadValue: { type: GraphQLBoolean },
diff --git a/src/type/__tests__/validation-test.ts b/src/type/__tests__/validation-test.ts
index a3c8b20451..9b57dacf74 100644
--- a/src/type/__tests__/validation-test.ts
+++ b/src/type/__tests__/validation-test.ts
@@ -20,7 +20,7 @@ import type {
   GraphQLEnumValueConfigMap,
 } from '../definition';
 import { GraphQLSchema } from '../schema';
-import { GraphQLString } from '../scalars';
+import { GraphQLString, GraphQLInt } from '../scalars';
 import { validateSchema, assertValidSchema } from '../validate';
 import { GraphQLDirective, assertDirective } from '../directives';
 import {
@@ -31,6 +31,7 @@ import {
   GraphQLUnionType,
   GraphQLEnumType,
   GraphQLInputObjectType,
+  GraphQLScalarType,
   assertScalarType,
   assertInterfaceType,
   assertObjectType,
@@ -783,7 +784,7 @@ describe('Type System: Input Objects must have fields', () => {
     expect(validateSchema(schema)).to.deep.equal([
       {
         message:
-          'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "nonNullSelf".',
+          'Invalid circular reference. The Input Object SomeInputObject references itself in the non-null field SomeInputObject.nonNullSelf.',
         locations: [{ line: 7, column: 9 }],
       },
     ]);
@@ -811,7 +812,7 @@ describe('Type System: Input Objects must have fields', () => {
     expect(validateSchema(schema)).to.deep.equal([
       {
         message:
-          'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "startLoop.nextInLoop.closeLoop".',
+          'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop.',
         locations: [
           { line: 7, column: 9 },
           { line: 11, column: 9 },
@@ -845,7 +846,7 @@ describe('Type System: Input Objects must have fields', () => {
     expect(validateSchema(schema)).to.deep.equal([
       {
         message:
-          'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "startLoop.closeLoop".',
+          'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.closeLoop.',
         locations: [
           { line: 7, column: 9 },
           { line: 11, column: 9 },
@@ -853,7 +854,7 @@ describe('Type System: Input Objects must have fields', () => {
       },
       {
         message:
-          'Cannot reference Input Object "AnotherInputObject" within itself through a series of non-null fields: "startSecondLoop.closeSecondLoop".',
+          'Invalid circular reference. The Input Object AnotherInputObject references itself via the non-null fields: AnotherInputObject.startSecondLoop, YetAnotherInputObject.closeSecondLoop.',
         locations: [
           { line: 12, column: 9 },
           { line: 16, column: 9 },
@@ -861,12 +862,294 @@ describe('Type System: Input Objects must have fields', () => {
       },
       {
         message:
-          'Cannot reference Input Object "YetAnotherInputObject" within itself through a series of non-null fields: "nonNullSelf".',
+          'Invalid circular reference. The Input Object YetAnotherInputObject references itself in the non-null field YetAnotherInputObject.nonNullSelf.',
         locations: [{ line: 17, column: 9 }],
       },
     ]);
   });
 
+  it('accepts Input Objects with default values without circular references (SDL)', () => {
+    const validSchema = buildSchema(`
+      type Query {
+        field(arg1: A, arg2: B): String
+      }
+
+      input A {
+        x: A = null
+        y: A = { x: null, y: null }
+        z: [A] = []
+      }
+
+      input B {
+        x: B2! = {}
+        y: String = "abc"
+        z: Custom = {}
+      }
+
+      input B2 {
+        x: B3 = {}
+      }
+
+      input B3 {
+        x: B = { x: { x: null } }
+      }
+
+      scalar Custom
+    `);
+
+    expect(validateSchema(validSchema)).to.deep.equal([]);
+  });
+
+  it('accepts Input Objects with default values without circular references (programmatic)', () => {
+    const AType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'A',
+      fields: () => ({
+        x: { type: AType, defaultValue: null },
+        y: { type: AType, defaultValue: { x: null, y: null } },
+        z: { type: new GraphQLList(AType), defaultValue: [] },
+      }),
+    });
+
+    const BType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B',
+      fields: () => ({
+        x: { type: new GraphQLNonNull(B2Type), defaultValue: {} },
+        y: { type: GraphQLString, defaultValue: 'abc' },
+        z: { type: CustomType, defaultValue: {} },
+      }),
+    });
+
+    const B2Type: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B2',
+      fields: () => ({
+        x: { type: B3Type, defaultValue: {} },
+      }),
+    });
+
+    const B3Type: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B3',
+      fields: () => ({
+        x: { type: BType, defaultValue: { x: { x: null } } },
+      }),
+    });
+
+    const CustomType = new GraphQLScalarType({ name: 'Custom' });
+
+    const validSchema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'Query',
+        fields: {
+          field: {
+            type: GraphQLString,
+            args: {
+              arg1: { type: AType },
+              arg2: { type: BType },
+            },
+          },
+        },
+      }),
+    });
+
+    expect(validateSchema(validSchema)).to.deep.equal([]);
+  });
+
+  it('rejects Input Objects with default value circular reference (SDL)', () => {
+    const invalidSchema = buildSchema(`
+      type Query {
+        field(arg1: A, arg2: B, arg3: C, arg4: D, arg5: E): String
+      }
+
+      input A {
+        x: A = {}
+      }
+
+      input B {
+        x: B2 = {}
+      }
+
+      input B2 {
+        x: B3 = {}
+      }
+
+      input B3 {
+        x: B = {}
+      }
+
+      input C {
+        x: [C] = [{}]
+      }
+
+      input D {
+        x: D = { x: { x: {} } }
+      }
+
+      input E {
+        x: E = { x: null }
+        y: E = { y: null }
+      }
+
+      input F {
+        x: F2! = {}
+      }
+
+      input F2 {
+        x: F = { x: {} }
+      }
+    `);
+
+    expect(validateSchema(invalidSchema)).to.deep.equal([
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field A.x references itself.',
+        locations: [{ line: 7, column: 16 }],
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field B.x references itself via the default values of: B2.x, B3.x.',
+        locations: [
+          { line: 11, column: 17 },
+          { line: 15, column: 17 },
+          { line: 19, column: 16 },
+        ],
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field C.x references itself.',
+        locations: [{ line: 23, column: 18 }],
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field D.x references itself.',
+        locations: [{ line: 27, column: 16 }],
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field E.x references itself via the default values of: E.y.',
+        locations: [
+          { line: 31, column: 16 },
+          { line: 32, column: 16 },
+        ],
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field F2.x references itself.',
+        locations: [{ line: 40, column: 16 }],
+      },
+    ]);
+  });
+
+  it('rejects Input Objects with default value circular reference (programmatic)', () => {
+    const AType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'A',
+      fields: () => ({
+        x: { type: AType, defaultValue: {} },
+      }),
+    });
+
+    const BType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B',
+      fields: () => ({
+        x: { type: B2Type, defaultValue: {} },
+      }),
+    });
+
+    const B2Type: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B2',
+      fields: () => ({
+        x: { type: B3Type, defaultValue: {} },
+      }),
+    });
+
+    const B3Type: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'B3',
+      fields: () => ({
+        x: { type: BType, defaultValue: {} },
+      }),
+    });
+
+    const CType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'C',
+      fields: () => ({
+        x: { type: new GraphQLList(CType), defaultValue: [{}] },
+      }),
+    });
+
+    const DType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'D',
+      fields: () => ({
+        x: { type: DType, defaultValue: { x: { x: {} } } },
+      }),
+    });
+
+    const EType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'E',
+      fields: () => ({
+        x: { type: EType, defaultValue: { x: null } },
+        y: { type: EType, defaultValue: { y: null } },
+      }),
+    });
+
+    const FType: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'F',
+      fields: () => ({
+        x: { type: new GraphQLNonNull(F2Type), defaultValue: {} },
+      }),
+    });
+
+    const F2Type: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'F2',
+      fields: () => ({
+        x: { type: FType, defaultValue: { x: {} } },
+      }),
+    });
+
+    const invalidSchema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'Query',
+        fields: {
+          field: {
+            type: GraphQLString,
+            args: {
+              arg1: { type: AType },
+              arg2: { type: BType },
+              arg3: { type: CType },
+              arg4: { type: DType },
+              arg5: { type: EType },
+              arg6: { type: FType },
+            },
+          },
+        },
+      }),
+    });
+
+    expect(validateSchema(invalidSchema)).to.deep.equal([
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field A.x references itself.',
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field B.x references itself via the default values of: B2.x, B3.x.',
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field C.x references itself.',
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field D.x references itself.',
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field E.x references itself via the default values of: E.y.',
+      },
+      {
+        message:
+          'Invalid circular reference. The default value of Input Object field F2.x references itself.',
+      },
+    ]);
+  });
+
   it('rejects an Input Object type with incorrectly typed fields', () => {
     const schema = buildSchema(`
       type Query {
@@ -899,7 +1182,7 @@ describe('Type System: Input Objects must have fields', () => {
     ]);
   });
 
-  it('rejects an Input Object type with required argument that is deprecated', () => {
+  it('rejects an Input Object type with required field that is deprecated', () => {
     const schema = buildSchema(`
       type Query {
         field(arg: SomeInputObject): String
@@ -1596,6 +1879,132 @@ describe('Type System: Arguments must have input types', () => {
   });
 });
 
+describe('Type System: Argument default values must be valid', () => {
+  it('rejects an argument with invalid default values (SDL)', () => {
+    const schema = buildSchema(`
+      type Query {
+        field(arg: Int = 3.14): Int
+      }
+
+      directive @bad(arg: Int = 2.718) on FIELD
+    `);
+
+    expect(validateSchema(schema)).to.deep.equal([
+      {
+        message:
+          '@bad(arg:) has invalid default value: Int cannot represent non-integer value: 2.718',
+        locations: [{ line: 6, column: 33 }],
+      },
+      {
+        message:
+          'Query.field(arg:) has invalid default value: Int cannot represent non-integer value: 3.14',
+        locations: [{ line: 3, column: 26 }],
+      },
+    ]);
+  });
+
+  it('rejects an argument with invalid default values (programmatic)', () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'Query',
+        fields: {
+          field: {
+            type: GraphQLInt,
+            args: {
+              arg: { type: GraphQLInt, defaultValue: 3.14 },
+            },
+          },
+        },
+      }),
+      directives: [
+        new GraphQLDirective({
+          name: 'bad',
+          args: {
+            arg: { type: GraphQLInt, defaultValue: 2.718 },
+          },
+          locations: ['FIELD'],
+        }),
+      ],
+    });
+
+    expect(validateSchema(schema)).to.deep.equal([
+      {
+        message:
+          '@bad(arg:) has invalid default value: Int cannot represent non-integer value: 2.718',
+      },
+      {
+        message:
+          'Query.field(arg:) has invalid default value: Int cannot represent non-integer value: 3.14',
+      },
+    ]);
+  });
+
+  it('Attempts to offer a suggested fix if possible', () => {
+    const Exotic = Symbol('Exotic');
+
+    const testEnum = new GraphQLEnumType({
+      name: 'TestEnum',
+      values: {
+        ONE: { value: 1 },
+        TWO: { value: Exotic },
+      },
+    });
+
+    const testInput: GraphQLInputObjectType = new GraphQLInputObjectType({
+      name: 'TestInput',
+      fields: () => ({
+        self: { type: testInput },
+        string: { type: new GraphQLNonNull(new GraphQLList(GraphQLString)) },
+        enum: { type: new GraphQLList(testEnum) },
+      }),
+    });
+
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'Query',
+        fields: {
+          field: {
+            type: GraphQLInt,
+            args: {
+              argWithPossibleFix: {
+                type: testInput,
+                defaultValue: { self: null, string: [1], enum: Exotic },
+              },
+              argWithInvalidPossibleFix: {
+                type: testInput,
+                defaultValue: { string: null },
+              },
+              argWithoutPossibleFix: {
+                type: testInput,
+                defaultValue: { enum: 'Exotic' },
+              },
+            },
+          },
+        },
+      }),
+    });
+
+    expect(validateSchema(schema)).to.deep.equal([
+      {
+        message:
+          'Query.field(argWithPossibleFix:) has invalid default value: { self: null, string: [1], enum: Symbol(Exotic) }. Did you mean: { self: null, string: ["1"], enum: ["TWO"] }?',
+      },
+      {
+        message:
+          'Query.field(argWithInvalidPossibleFix:) has invalid default value at .string: Expected value of non-null type [String]! not to be null.',
+      },
+      {
+        message:
+          'Query.field(argWithoutPossibleFix:) has invalid default value: Expected value of type TestInput to include required field "string", found: { enum: "Exotic" }.',
+      },
+      {
+        message:
+          'Query.field(argWithoutPossibleFix:) has invalid default value at .enum: Value "Exotic" does not exist in "TestEnum" enum.',
+      },
+    ]);
+  });
+});
+
 describe('Type System: Input Object fields must have input types', () => {
   function schemaWithInputField(
     inputFieldConfig: GraphQLInputFieldConfig,
@@ -1692,6 +2101,61 @@ describe('Type System: Input Object fields must have input types', () => {
   });
 });
 
+describe('Type System: Input Object field default values must be valid', () => {
+  it('rejects an Input Object field with invalid default values (SDL)', () => {
+    const schema = buildSchema(`
+    type Query {
+      field(arg: SomeInputObject): Int
+    }
+
+    input SomeInputObject {
+      field: Int = 3.14
+    }
+  `);
+
+    expect(validateSchema(schema)).to.deep.equal([
+      {
+        message:
+          'SomeInputObject.field has invalid default value: Int cannot represent non-integer value: 3.14',
+        locations: [{ line: 7, column: 20 }],
+      },
+    ]);
+  });
+
+  it('rejects an Input Object field with invalid default values (programmatic)', () => {
+    const someInputObject = new GraphQLInputObjectType({
+      name: 'SomeInputObject',
+      fields: {
+        field: {
+          type: GraphQLInt,
+          defaultValue: 3.14,
+        },
+      },
+    });
+
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'Query',
+        fields: {
+          field: {
+            type: GraphQLInt,
+            args: {
+              arg: { type: someInputObject },
+            },
+          },
+        },
+      }),
+    });
+
+    expect(validateSchema(schema)).to.deep.equal([
+      {
+        message:
+          'SomeInputObject.field has invalid default value: Int cannot represent non-integer value: 3.14',
+      },
+    ]);
+  });
+});
+
 describe('Objects must adhere to Interface they implement', () => {
   it('accepts an Object which implements an Interface', () => {
     const schema = buildSchema(`
diff --git a/src/type/introspection.ts b/src/type/introspection.ts
index fd4f81c15b..c16c245ce0 100644
--- a/src/type/introspection.ts
+++ b/src/type/introspection.ts
@@ -3,7 +3,8 @@ import { invariant } from '../jsutils/invariant';
 
 import { print } from '../language/printer';
 import { DirectiveLocation } from '../language/directiveLocation';
-import { astFromValue } from '../utilities/astFromValue';
+
+import { valueToLiteral } from '../utilities/valueToLiteral';
 
 import type { GraphQLSchema } from './schema';
 import type { GraphQLDirective } from './directives';
@@ -388,7 +389,7 @@ export const __InputValue: GraphQLObjectType = new GraphQLObjectType({
             return null;
           }
           const literal =
-            defaultValue.literal ?? astFromValue(defaultValue.value, type);
+            defaultValue.literal ?? valueToLiteral(defaultValue.value, type);
           invariant(literal, 'Invalid default value');
           return print(literal);
         },
diff --git a/src/type/validate.ts b/src/type/validate.ts
index a6de555816..8433dd3dd3 100644
--- a/src/type/validate.ts
+++ b/src/type/validate.ts
@@ -1,5 +1,12 @@
-import { inspect } from '../jsutils/inspect';
 import type { Maybe } from '../jsutils/Maybe';
+import { hasOwnProperty } from '../jsutils/hasOwnProperty';
+import { inspect } from '../jsutils/inspect';
+import { invariant } from '../jsutils/invariant';
+import { isIterableObject } from '../jsutils/isIterableObject';
+import { isObjectLike } from '../jsutils/isObjectLike';
+import { keyMap } from '../jsutils/keyMap';
+import { mapValue } from '../jsutils/mapValue';
+import { printPathArray } from '../jsutils/printPathArray';
 
 import { GraphQLError } from '../error/GraphQLError';
 import { locatedError } from '../error/locatedError';
@@ -7,6 +14,7 @@ import { locatedError } from '../error/locatedError';
 import type {
   ASTNode,
   NamedTypeNode,
+  ConstValueNode,
   DirectiveNode,
   OperationTypeNode,
   ObjectTypeDefinitionNode,
@@ -16,9 +24,14 @@ import type {
   UnionTypeDefinitionNode,
   UnionTypeExtensionNode,
 } from '../language/ast';
+import { Kind } from '../language/kinds';
 
 import { isValidNameError } from '../utilities/assertValidName';
 import { isEqualType, isTypeSubTypeOf } from '../utilities/typeComparators';
+import {
+  validateInputValue,
+  validateInputLiteral,
+} from '../utilities/validateInputValue';
 
 import type { GraphQLSchema } from './schema';
 import type {
@@ -28,11 +41,15 @@ import type {
   GraphQLEnumType,
   GraphQLInputObjectType,
   GraphQLInputField,
+  GraphQLInputType,
+  GraphQLArgument,
 } from './definition';
 import { assertSchema } from './schema';
 import { isIntrospectionType } from './introspection';
 import { isDirective, GraphQLDeprecatedDirective } from './directives';
 import {
+  assertLeafType,
+  getNamedType,
   isObjectType,
   isInterfaceType,
   isUnionType,
@@ -40,6 +57,7 @@ import {
   isInputObjectType,
   isNamedType,
   isNonNullType,
+  isListType,
   isInputType,
   isOutputType,
   isRequiredArgument,
@@ -196,8 +214,126 @@ function validateDirectives(context: SchemaValidationContext): void {
           arg.astNode?.type,
         ]);
       }
+
+      validateDefaultValue(context, arg);
+    }
+  }
+}
+
+function validateDefaultValue(
+  context: SchemaValidationContext,
+  inputValue: GraphQLArgument | GraphQLInputField,
+): void {
+  const defaultValue = inputValue.defaultValue;
+
+  if (!defaultValue) {
+    return;
+  }
+
+  if (defaultValue.literal) {
+    validateInputLiteral(
+      defaultValue.literal,
+      inputValue.type,
+      undefined,
+      (error, path) => {
+        context.reportError(
+          `${inputValue} has invalid default value${printPathArray(path)}: ${
+            error.message
+          }`,
+          error.nodes,
+        );
+      },
+    );
+  } else {
+    const errors: Array<[GraphQLError, ReadonlyArray<string | number>]> = [];
+    validateInputValue(defaultValue.value, inputValue.type, (error, path) => {
+      errors.push([error, path]);
+    });
+
+    // If there were validation errors, check to see if it can be "uncoerced"
+    // and then correctly validated. If so, report a clear error with a path
+    // to resolution.
+    if (errors.length > 0) {
+      try {
+        const uncoercedValue = uncoerceDefaultValue(
+          defaultValue.value,
+          inputValue.type,
+        );
+
+        const uncoercedErrors = [];
+        validateInputValue(uncoercedValue, inputValue.type, (error, path) => {
+          uncoercedErrors.push([error, path]);
+        });
+
+        if (uncoercedErrors.length === 0) {
+          context.reportError(
+            `${inputValue} has invalid default value: ${inspect(
+              defaultValue.value,
+            )}. Did you mean: ${inspect(uncoercedValue)}?`,
+            inputValue.astNode?.defaultValue,
+          );
+          return;
+        }
+      } catch (_error) {
+        // ignore
+      }
+    }
+
+    // Otherwise report the original set of errors.
+    for (const [error, path] of errors) {
+      context.reportError(
+        `${inputValue} has invalid default value${printPathArray(path)}: ${
+          error.message
+        }`,
+        inputValue.astNode?.defaultValue,
+      );
+    }
+  }
+}
+
+/**
+ * Historically GraphQL.js allowed default values to be provided as
+ * assumed-coerced "internal" values, however default values should be provided
+ * as "external" pre-coerced values. `uncoerceDefaultValue()` will convert such
+ * "internal" values to "external" values to display as part of validation.
+ *
+ * This performs the "opposite" of `coerceInputValue()`. Given an "internal"
+ * coerced value, reverse the process to provide an "external" uncoerced value.
+ */
+function uncoerceDefaultValue(value: unknown, type: GraphQLInputType): unknown {
+  if (isNonNullType(type)) {
+    return uncoerceDefaultValue(value, type.ofType);
+  }
+
+  if (value === null) {
+    return null;
+  }
+
+  if (isListType(type)) {
+    if (isIterableObject(value)) {
+      return Array.from(value, (itemValue) =>
+        uncoerceDefaultValue(itemValue, type.ofType),
+      );
     }
+    return [uncoerceDefaultValue(value, type.ofType)];
   }
+
+  if (isInputObjectType(type)) {
+    invariant(isObjectLike(value));
+    const fieldDefs = type.getFields();
+    return mapValue(value, (fieldValue, fieldName) => {
+      invariant(fieldName in fieldDefs);
+      return uncoerceDefaultValue(fieldValue, fieldDefs[fieldName].type);
+    });
+  }
+
+  assertLeafType(type);
+
+  // For most leaf types (Scalars, Enums), result coercion ("serialize") is
+  // the inverse of input coercion ("parseValue") and will produce an
+  // "external" value. Historically, this method was also used as part of the
+  // now-removed "astFromValue" to perform the same behavior.
+  return type.serialize(value);
 }
 
 function validateName(
@@ -212,8 +348,11 @@ function validateName(
 }
 
 function validateTypes(context: SchemaValidationContext): void {
-  const validateInputObjectCircularRefs =
-    createInputObjectCircularRefsValidator(context);
+  // Ensure Input Objects do not contain non-nullable circular references.
+  const validateInputObjectNonNullCircularRefs =
+    createInputObjectNonNullCircularRefsValidator(context);
+  const validateInputObjectDefaultValueCircularRefs =
+    createInputObjectDefaultValueCircularRefsValidator(context);
   const typeMap = context.schema.getTypeMap();
   for (const type of Object.values(typeMap)) {
     // Ensure all provided types are in fact GraphQL type.
@@ -252,8 +391,12 @@ function validateTypes(context: SchemaValidationContext): void {
       // Ensure Input Object fields are valid.
       validateInputFields(context, type);
 
-      // Ensure Input Objects do not contain non-nullable circular references
-      validateInputObjectCircularRefs(type);
+      // Ensure Input Objects do not contain invalid field circular references.
+      // Ensure Input Objects do not contain non-nullable circular references.
+      validateInputObjectNonNullCircularRefs(type);
+
+      // Ensure Input Objects do not contain invalid default value circular references.
+      validateInputObjectDefaultValueCircularRefs(type);
     }
   }
 }
@@ -305,6 +448,8 @@ function validateFields(
           arg.astNode?.type,
         ]);
       }
+
+      validateDefaultValue(context, arg);
     }
   }
 }
@@ -405,8 +550,6 @@ function validateTypeImplementsInterface(
           ],
         );
       }
-
-      // TODO: validate default values?
     }
 
     // Assert additional arguments must not be required.
@@ -521,7 +664,7 @@ function validateInputFields(
     );
   }
 
-  // Ensure the arguments are valid
+  // Ensure the input fields are valid
   for (const field of fields) {
     // Ensure they are named correctly.
     validateName(context, field);
@@ -545,10 +688,12 @@ function validateInputFields(
         ],
       );
     }
+
+    validateDefaultValue(context, field);
   }
 }
 
-function createInputObjectCircularRefsValidator(
+function createInputObjectNonNullCircularRefsValidator(
   context: SchemaValidationContext,
 ): (inputObj: GraphQLInputObjectType) => void {
   // Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
@@ -586,9 +731,13 @@ function createInputObjectCircularRefsValidator(
           detectCycleRecursive(fieldType);
         } else {
           const cyclePath = fieldPath.slice(cycleIndex);
-          const pathStr = cyclePath.map((fieldObj) => fieldObj.name).join('.');
+          const pathStr = cyclePath.join(', ');
           context.reportError(
-            `Cannot reference Input Object "${fieldType}" within itself through a series of non-null fields: "${pathStr}".`,
+            `Invalid circular reference. The Input Object ${fieldType} references itself ${
+              cyclePath.length > 1
+                ? 'via the non-null fields:'
+                : 'in the non-null field'
+            } ${pathStr}.`,
             cyclePath.map((fieldObj) => fieldObj.astNode),
           );
         }
@@ -600,6 +749,154 @@ function createInputObjectCircularRefsValidator(
   }
 }
 
+function createInputObjectDefaultValueCircularRefsValidator(
+  context: SchemaValidationContext,
+): (inputObj: GraphQLInputObjectType) => void {
+  // Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
+  // Tracks already visited types to maintain O(N) and to ensure that cycles
+  // are not redundantly reported.
+  const visitedFields = Object.create(null);
+
+  // Array of coordinates and default values used to produce meaningful errors.
+  const fieldPath: Array<
+    [coordinate: string, defaultValue: ConstValueNode | undefined]
+  > = [];
+
+  // Position in the path
+  const fieldPathIndex = Object.create(null);
+
+  // This does a straight-forward DFS to find cycles.
+  // It does not terminate when a cycle was found but continues to explore
+  // the graph to find all possible cycles.
+  return function validateInputObjectDefaultValueCircularRefs(
+    inputObj: GraphQLInputObjectType,
+  ): void {
+    // Start with an empty object as a way to visit every field in this input
+    // object type and apply every default value.
+    return detectValueDefaultValueCycle(inputObj, {});
+  };
+
+  function detectValueDefaultValueCycle(
+    inputObj: GraphQLInputObjectType,
+    defaultValue: unknown,
+  ): void {
+    // If the value is a List, recursively check each entry for a cycle.
+    // Otherwise, only object values can contain a cycle.
+    if (isIterableObject(defaultValue)) {
+      for (const itemValue of defaultValue) {
+        detectValueDefaultValueCycle(inputObj, itemValue);
+      }
+      return;
+    } else if (!isObjectLike(defaultValue)) {
+      return;
+    }
+
+    // Check each defined field for a cycle.
+    for (const field of Object.values(inputObj.getFields())) {
+      const namedFieldType = getNamedType(field.type);
+
+      // Only input object type fields can result in a cycle.
+      if (!isInputObjectType(namedFieldType)) {
+        continue;
+      }
+
+      if (hasOwnProperty(defaultValue, field.name)) {
+        // If the provided value has this field defined, recursively check it
+        // for cycles.
+        detectValueDefaultValueCycle(namedFieldType, defaultValue[field.name]);
+      } else {
+        // Otherwise check this field's default value for cycles.
+        detectFieldDefaultValueCycle(field, namedFieldType);
+      }
+    }
+  }
+
+  function detectLiteralDefaultValueCycle(
+    inputObj: GraphQLInputObjectType,
+    defaultValue: ConstValueNode,
+  ): void {
+    // If the value is a List, recursively check each entry for a cycle.
+    // Otherwise, only object values can contain a cycle.
+    if (defaultValue.kind === Kind.LIST) {
+      for (const itemLiteral of defaultValue.values) {
+        detectLiteralDefaultValueCycle(inputObj, itemLiteral);
+      }
+      return;
+    } else if (defaultValue.kind !== Kind.OBJECT) {
+      return;
+    }
+
+    // Check each defined field for a cycle.
+    const fieldNodes = keyMap(defaultValue.fields, (field) => field.name.value);
+    for (const field of Object.values(inputObj.getFields())) {
+      const namedFieldType = getNamedType(field.type);
+
+      // Only input object type fields can result in a cycle.
+      if (!isInputObjectType(namedFieldType)) {
+        continue;
+      }
+
+      if (hasOwnProperty(fieldNodes, field.name)) {
+        // If the provided value has this field defined, recursively check it
+        // for cycles.
+        detectLiteralDefaultValueCycle(
+          namedFieldType,
+          fieldNodes[field.name].value,
+        );
+      } else {
+        // Otherwise check this field's default value for cycles.
+        detectFieldDefaultValueCycle(field, namedFieldType);
+      }
+    }
+  }
+
+  function detectFieldDefaultValueCycle(
+    field: GraphQLInputField,
+    fieldType: GraphQLInputObjectType,
+  ): void {
+    // Only a field with a default value can result in a cycle.
+    const defaultValue = field.defaultValue;
+    if (defaultValue === undefined) {
+      return;
+    }
+
+    const fieldCoordinate = String(field);
+
+    // Check to see if there is cycle.
+    const cycleIndex = fieldPathIndex[fieldCoordinate];
+    if (cycleIndex > 0) {
+      context.reportError(
+        `Invalid circular reference. The default value of Input Object field ${field} references itself${
+          cycleIndex < fieldPath.length
+            ? ` via the default values of: ${fieldPath
+                .slice(cycleIndex)
+                .map(([coordinate]) => coordinate)
+                .join(', ')}`
+            : ''
+        }.`,
+        fieldPath.slice(cycleIndex - 1).map(([, node]) => node),
+      );
+      return;
+    }
+
+    // Recurse into this field's default value once, tracking the path.
+    if (!visitedFields[fieldCoordinate]) {
+      visitedFields[fieldCoordinate] = true;
+      fieldPathIndex[fieldCoordinate] = fieldPath.push([
+        fieldCoordinate,
+        field.astNode?.defaultValue,
+      ]);
+      if (defaultValue.literal) {
+        detectLiteralDefaultValueCycle(fieldType, defaultValue.literal);
+      } else {
+        detectValueDefaultValueCycle(fieldType, defaultValue.value);
+      }
+      fieldPath.pop();
+      fieldPathIndex[fieldCoordinate] = undefined;
+    }
+  }
+}
+
 function getAllImplementsInterfaceNodes(
   type: GraphQLObjectType | GraphQLInterfaceType,
   iface: GraphQLInterfaceType,
diff --git a/src/utilities/__tests__/astFromValue-test.ts b/src/utilities/__tests__/astFromValue-test.ts
deleted file mode 100644
index 3641f00227..0000000000
--- a/src/utilities/__tests__/astFromValue-test.ts
+++ /dev/null
@@ -1,379 +0,0 @@
-import { expect } from 'chai';
-import { describe, it } from 'mocha';
-
-import {
-  GraphQLID,
-  GraphQLInt,
-  GraphQLFloat,
-  GraphQLString,
-  GraphQLBoolean,
-} from '../../type/scalars';
-import {
-  GraphQLList,
-  GraphQLNonNull,
-  GraphQLScalarType,
-  GraphQLEnumType,
-  GraphQLInputObjectType,
-} from '../../type/definition';
-
-import { astFromValue } from '../astFromValue';
-
-describe('astFromValue', () => {
-  it('converts boolean values to ASTs', () => {
-    expect(astFromValue(true, GraphQLBoolean)).to.deep.equal({
-      kind: 'BooleanValue',
-      value: true,
-    });
-
-    expect(astFromValue(false, GraphQLBoolean)).to.deep.equal({
-      kind: 'BooleanValue',
-      value: false,
-    });
-
-    expect(astFromValue(undefined, GraphQLBoolean)).to.deep.equal(null);
-
-    expect(astFromValue(null, GraphQLBoolean)).to.deep.equal({
-      kind: 'NullValue',
-    });
-
-    expect(astFromValue(0, GraphQLBoolean)).to.deep.equal({
-      kind: 'BooleanValue',
-      value: false,
-    });
-
-    expect(astFromValue(1, GraphQLBoolean)).to.deep.equal({
-      kind: 'BooleanValue',
-      value: true,
-    });
-
-    const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean);
-    expect(astFromValue(0, NonNullBoolean)).to.deep.equal({
-      kind: 'BooleanValue',
-      value: false,
-    });
-  });
-
-  it('converts Int values to Int ASTs', () => {
-    expect(astFromValue(-1, GraphQLInt)).to.deep.equal({
-      kind: 'IntValue',
-      value: '-1',
-    });
-
-    expect(astFromValue(123.0, GraphQLInt)).to.deep.equal({
-      kind: 'IntValue',
-      value: '123',
-    });
-
-    expect(astFromValue(1e4, GraphQLInt)).to.deep.equal({
-      kind: 'IntValue',
-      value: '10000',
-    });
-
-    // GraphQL spec does not allow coercing non-integer values to Int to avoid
-    // accidental data loss.
-    expect(() => astFromValue(123.5, GraphQLInt)).to.throw(
-      'Int cannot represent non-integer value: 123.5',
-    );
-
-    // Note: outside the bounds of 32bit signed int.
-    expect(() => astFromValue(1e40, GraphQLInt)).to.throw(
-      'Int cannot represent non 32-bit signed integer value: 1e+40',
-    );
-
-    expect(() => astFromValue(NaN, GraphQLInt)).to.throw(
-      'Int cannot represent non-integer value: NaN',
-    );
-  });
-
-  it('converts Float values to Int/Float ASTs', () => {
-    expect(astFromValue(-1, GraphQLFloat)).to.deep.equal({
-      kind: 'IntValue',
-      value: '-1',
-    });
-
-    expect(astFromValue(123.0, GraphQLFloat)).to.deep.equal({
-      kind: 'IntValue',
-      value: '123',
-    });
-
-    expect(astFromValue(123.5, GraphQLFloat)).to.deep.equal({
-      kind: 'FloatValue',
-      value: '123.5',
-    });
-
-    expect(astFromValue(1e4, GraphQLFloat)).to.deep.equal({
-      kind: 'IntValue',
-      value: '10000',
-    });
-
-    expect(astFromValue(1e40, GraphQLFloat)).to.deep.equal({
-      kind: 'FloatValue',
-      value: '1e+40',
-    });
-  });
-
-  it('converts String values to String ASTs', () => {
-    expect(astFromValue('hello', GraphQLString)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'hello',
-    });
-
-    expect(astFromValue('VALUE', GraphQLString)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'VALUE',
-    });
-
-    expect(astFromValue('VA\nLUE', GraphQLString)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'VA\nLUE',
-    });
-
-    expect(astFromValue(123, GraphQLString)).to.deep.equal({
-      kind: 'StringValue',
-      value: '123',
-    });
-
-    expect(astFromValue(false, GraphQLString)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'false',
-    });
-
-    expect(astFromValue(null, GraphQLString)).to.deep.equal({
-      kind: 'NullValue',
-    });
-
-    expect(astFromValue(undefined, GraphQLString)).to.deep.equal(null);
-  });
-
-  it('converts ID values to Int/String ASTs', () => {
-    expect(astFromValue('hello', GraphQLID)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'hello',
-    });
-
-    expect(astFromValue('VALUE', GraphQLID)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'VALUE',
-    });
-
-    // Note: EnumValues cannot contain non-identifier characters
-    expect(astFromValue('VA\nLUE', GraphQLID)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'VA\nLUE',
-    });
-
-    // Note: IntValues are used when possible.
-    expect(astFromValue(-1, GraphQLID)).to.deep.equal({
-      kind: 'IntValue',
-      value: '-1',
-    });
-
-    expect(astFromValue(123, GraphQLID)).to.deep.equal({
-      kind: 'IntValue',
-      value: '123',
-    });
-
-    expect(astFromValue('123', GraphQLID)).to.deep.equal({
-      kind: 'IntValue',
-      value: '123',
-    });
-
-    expect(astFromValue('01', GraphQLID)).to.deep.equal({
-      kind: 'StringValue',
-      value: '01',
-    });
-
-    expect(() => astFromValue(false, GraphQLID)).to.throw(
-      'ID cannot represent value: false',
-    );
-
-    expect(astFromValue(null, GraphQLID)).to.deep.equal({ kind: 'NullValue' });
-
-    expect(astFromValue(undefined, GraphQLID)).to.deep.equal(null);
-  });
-
-  it('converts using serialize from a custom scalar type', () => {
-    const passthroughScalar = new GraphQLScalarType({
-      name: 'PassthroughScalar',
-      serialize(value) {
-        return value;
-      },
-    });
-
-    expect(astFromValue('value', passthroughScalar)).to.deep.equal({
-      kind: 'StringValue',
-      value: 'value',
-    });
-
-    expect(() => astFromValue(NaN, passthroughScalar)).to.throw(
-      'Cannot convert value to AST: NaN.',
-    );
-    expect(() => astFromValue(Infinity, passthroughScalar)).to.throw(
-      'Cannot convert value to AST: Infinity.',
-    );
-
-    const returnNullScalar = new GraphQLScalarType({
-      name: 'ReturnNullScalar',
-      serialize() {
-        return null;
-      },
-    });
-
-    expect(astFromValue('value', returnNullScalar)).to.equal(null);
-
-    class SomeClass {}
-
-    const returnCustomClassScalar = new GraphQLScalarType({
-      name: 'ReturnCustomClassScalar',
-      serialize() {
-        return new SomeClass();
-      },
-    });
-
-    expect(() => astFromValue('value', returnCustomClassScalar)).to.throw(
-      'Cannot convert value to AST: {}.',
-    );
-  });
-
-  it('does not converts NonNull values to NullValue', () => {
-    const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean);
-    expect(astFromValue(null, NonNullBoolean)).to.deep.equal(null);
-  });
-
-  const complexValue = { someArbitrary: 'complexValue' };
-
-  const myEnum = new GraphQLEnumType({
-    name: 'MyEnum',
-    values: {
-      HELLO: {},
-      GOODBYE: {},
-      COMPLEX: { value: complexValue },
-    },
-  });
-
-  it('converts string values to Enum ASTs if possible', () => {
-    expect(astFromValue('HELLO', myEnum)).to.deep.equal({
-      kind: 'EnumValue',
-      value: 'HELLO',
-    });
-
-    expect(astFromValue(complexValue, myEnum)).to.deep.equal({
-      kind: 'EnumValue',
-      value: 'COMPLEX',
-    });
-
-    // Note: case sensitive
-    expect(() => astFromValue('hello', myEnum)).to.throw(
-      'Enum "MyEnum" cannot represent value: "hello"',
-    );
-
-    // Note: Not a valid enum value
-    expect(() => astFromValue('UNKNOWN_VALUE', myEnum)).to.throw(
-      'Enum "MyEnum" cannot represent value: "UNKNOWN_VALUE"',
-    );
-  });
-
-  it('converts array values to List ASTs', () => {
-    expect(
-      astFromValue(['FOO', 'BAR'], new GraphQLList(GraphQLString)),
-    ).to.deep.equal({
-      kind: 'ListValue',
-      values: [
-        { kind: 'StringValue', value: 'FOO' },
-        { kind: 'StringValue', value: 'BAR' },
-      ],
-    });
-
-    expect(
-      astFromValue(['HELLO', 'GOODBYE'], new GraphQLList(myEnum)),
-    ).to.deep.equal({
-      kind: 'ListValue',
-      values: [
-        { kind: 'EnumValue', value: 'HELLO' },
-        { kind: 'EnumValue', value: 'GOODBYE' },
-      ],
-    });
-
-    function* listGenerator() {
-      yield 1;
-      yield 2;
-      yield 3;
-    }
-
-    expect(
-      astFromValue(listGenerator(), new GraphQLList(GraphQLInt)),
-    ).to.deep.equal({
-      kind: 'ListValue',
-      values: [
-        { kind: 'IntValue', value: '1' },
-        { kind: 'IntValue', value: '2' },
-        { kind: 'IntValue', value: '3' },
-      ],
-    });
-  });
-
-  it('converts list singletons', () => {
-    expect(astFromValue('FOO', new GraphQLList(GraphQLString))).to.deep.equal({
-      kind: 'StringValue',
-      value: 'FOO',
-    });
-  });
-
-  it('skip invalid list items', () => {
-    const ast = astFromValue(
-      ['FOO', null, 'BAR'],
-      new GraphQLList(new GraphQLNonNull(GraphQLString)),
-    );
-
-    expect(ast).to.deep.equal({
-      kind: 'ListValue',
-      values: [
-        { kind: 'StringValue', value: 'FOO' },
-        { kind: 'StringValue', value: 'BAR' },
-      ],
-    });
-  });
-
-  const inputObj = new GraphQLInputObjectType({
-    name: 'MyInputObj',
-    fields: {
-      foo: { type: GraphQLFloat },
-      bar: { type: myEnum },
-    },
-  });
-
-  it('converts input objects', () => {
-    expect(astFromValue({ foo: 3, bar: 'HELLO' }, inputObj)).to.deep.equal({
-      kind: 'ObjectValue',
-      fields: [
-        {
-          kind: 'ObjectField',
-          name: { kind: 'Name', value: 'foo' },
-          value: { kind: 'IntValue', value: '3' },
-        },
-        {
-          kind: 'ObjectField',
-          name: { kind: 'Name', value: 'bar' },
-          value: { kind: 'EnumValue', value: 'HELLO' },
-        },
-      ],
-    });
-  });
-
-  it('converts input objects with explicit nulls', () => {
-    expect(astFromValue({ foo: null }, inputObj)).to.deep.equal({
-      kind: 'ObjectValue',
-      fields: [
-        {
-          kind: 'ObjectField',
-          name: { kind: 'Name', value: 'foo' },
-          value: { kind: 'NullValue' },
-        },
-      ],
-    });
-  });
-
-  it('does not converts non-object values as input objects', () => {
-    expect(astFromValue(5, inputObj)).to.equal(null);
-  });
-});
diff --git a/src/utilities/astFromValue.ts b/src/utilities/astFromValue.ts
deleted file mode 100644
index 7158f7bd0f..0000000000
--- a/src/utilities/astFromValue.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import { inspect } from '../jsutils/inspect';
-import { invariant } from '../jsutils/invariant';
-import { isObjectLike } from '../jsutils/isObjectLike';
-import { isIterableObject } from '../jsutils/isIterableObject';
-import type { Maybe } from '../jsutils/Maybe';
-
-import type { ConstValueNode } from '../language/ast';
-import { Kind } from '../language/kinds';
-
-import type { GraphQLInputType } from '../type/definition';
-import { GraphQLID } from '../type/scalars';
-import {
-  isLeafType,
-  isEnumType,
-  isInputObjectType,
-  isListType,
-  isNonNullType,
-} from '../type/definition';
-
-/**
- * Produces a GraphQL Value AST given a JavaScript object.
- * Function will match JavaScript/JSON values to GraphQL AST schema format
- * by using suggested GraphQLInputType. For example:
- *
- *     astFromValue("value", GraphQLString)
- *
- * A GraphQL type must be provided, which will be used to interpret different
- * JavaScript values.
- *
- * | JSON Value    | GraphQL Value        |
- * | ------------- | -------------------- |
- * | Object        | Input Object         |
- * | Array         | List                 |
- * | Boolean       | Boolean              |
- * | String        | String / Enum Value  |
- * | Number        | Int / Float          |
- * | Unknown       | Enum Value           |
- * | null          | NullValue            |
- *
- */
-export function astFromValue(
-  value: unknown,
-  type: GraphQLInputType,
-): Maybe<ConstValueNode> {
-  if (isNonNullType(type)) {
-    const astValue = astFromValue(value, type.ofType);
-    if (astValue?.kind === Kind.NULL) {
-      return null;
-    }
-    return astValue;
-  }
-
-  // only explicit null, not undefined, NaN
-  if (value === null) {
-    return { kind: Kind.NULL };
-  }
-
-  // undefined
-  if (value === undefined) {
-    return null;
-  }
-
-  // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but
-  // the value is not an array, convert the value using the list's item type.
-  if (isListType(type)) {
-    const itemType = type.ofType;
-    if (isIterableObject(value)) {
-      const valuesNodes = [];
-      for (const item of value) {
-        const itemNode = astFromValue(item, itemType);
-        if (itemNode != null) {
-          valuesNodes.push(itemNode);
-        }
-      }
-      return { kind: Kind.LIST, values: valuesNodes };
-    }
-    return astFromValue(value, itemType);
-  }
-
-  // Populate the fields of the input object by creating ASTs from each value
-  // in the JavaScript object according to the fields in the input type.
-  if (isInputObjectType(type)) {
-    if (!isObjectLike(value)) {
-      return null;
-    }
-    const fieldNodes = [];
-    for (const field of Object.values(type.getFields())) {
-      const fieldValue = astFromValue(value[field.name], field.type);
-      if (fieldValue) {
-        fieldNodes.push({
-          kind: Kind.OBJECT_FIELD,
-          name: { kind: Kind.NAME, value: field.name },
-          value: fieldValue,
-        });
-      }
-    }
-    return { kind: Kind.OBJECT, fields: fieldNodes };
-  }
-
-  // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
-  if (isLeafType(type)) {
-    // Since value is an internally represented value, it must be serialized
-    // to an externally represented value before converting into an AST.
-    const serialized = type.serialize(value);
-    if (serialized == null) {
-      return null;
-    }
-
-    // Others serialize based on their corresponding JavaScript scalar types.
-    if (typeof serialized === 'boolean') {
-      return { kind: Kind.BOOLEAN, value: serialized };
-    }
-
-    // JavaScript numbers can be Int or Float values.
-    if (typeof serialized === 'number' && Number.isFinite(serialized)) {
-      const stringNum = String(serialized);
-      return integerStringRegExp.test(stringNum)
-        ? { kind: Kind.INT, value: stringNum }
-        : { kind: Kind.FLOAT, value: stringNum };
-    }
-
-    if (typeof serialized === 'string') {
-      // Enum types use Enum literals.
-      if (isEnumType(type)) {
-        return { kind: Kind.ENUM, value: serialized };
-      }
-
-      // ID types can use Int literals.
-      if (type === GraphQLID && integerStringRegExp.test(serialized)) {
-        return { kind: Kind.INT, value: serialized };
-      }
-
-      return {
-        kind: Kind.STRING,
-        value: serialized,
-      };
-    }
-
-    throw new TypeError(`Cannot convert value to AST: ${inspect(serialized)}.`);
-  }
-
-  // istanbul ignore next (Not reachable. All possible input types have been considered)
-  invariant(false, 'Unexpected input type: ' + inspect(type));
-}
-
-/**
- * IntValue:
- *   - NegativeSign? 0
- *   - NegativeSign? NonZeroDigit ( Digit+ )?
- */
-const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;
diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts
index b297dea262..5f04727493 100644
--- a/src/utilities/coerceInputValue.ts
+++ b/src/utilities/coerceInputValue.ts
@@ -253,7 +253,7 @@ export function coerceDefaultValue(
   if (coercedValue === undefined) {
     coercedValue = defaultValue.literal
       ? coerceInputLiteral(defaultValue.literal, type)
-      : defaultValue.value;
+      : coerceInputValue(defaultValue.value, type);
     invariant(coercedValue !== undefined);
     (defaultValue as any)._memoizedCoercedValue = coercedValue;
   }
diff --git a/src/utilities/findBreakingChanges.ts b/src/utilities/findBreakingChanges.ts
index fe058d0fd8..9abbaf59db 100644
--- a/src/utilities/findBreakingChanges.ts
+++ b/src/utilities/findBreakingChanges.ts
@@ -34,7 +34,7 @@ import {
   isRequiredInputField,
 } from '../type/definition';
 
-import { astFromValue } from './astFromValue';
+import { valueToLiteral } from './valueToLiteral';
 
 export const BreakingChangeType = Object.freeze({
   TYPE_REMOVED: 'TYPE_REMOVED',
@@ -532,7 +532,7 @@ function stringifyValue(
   defaultValue: GraphQLDefaultValueUsage,
   type: GraphQLInputType,
 ): string {
-  const ast = defaultValue.literal ?? astFromValue(defaultValue.value, type);
+  const ast = defaultValue.literal ?? valueToLiteral(defaultValue.value, type);
   invariant(ast);
   const sortedAST = visit(ast, {
     ObjectValue(objectNode) {
diff --git a/src/utilities/index.ts b/src/utilities/index.ts
index 6f25494c62..87667a845b 100644
--- a/src/utilities/index.ts
+++ b/src/utilities/index.ts
@@ -64,9 +64,6 @@ export { typeFromAST } from './typeFromAST';
 /** Create a JavaScript value from a GraphQL language AST without a type. */
 export { valueFromASTUntyped } from './valueFromASTUntyped';
 
-/** Create a GraphQL language AST from a JavaScript value. */
-export { astFromValue } from './astFromValue';
-
 /** A helper to use within recursive-descent visitors which need to be aware of the GraphQL type system. */
 export { TypeInfo, visitWithTypeInfo } from './TypeInfo';
 
diff --git a/src/utilities/printSchema.ts b/src/utilities/printSchema.ts
index 0f76080c62..b0b1e75767 100644
--- a/src/utilities/printSchema.ts
+++ b/src/utilities/printSchema.ts
@@ -33,7 +33,7 @@ import {
   isInputObjectType,
 } from '../type/definition';
 
-import { astFromValue } from './astFromValue';
+import { valueToLiteral } from './valueToLiteral';
 
 export function printSchema(schema: GraphQLSchema): string {
   return printFilteredSchema(
@@ -263,7 +263,7 @@ function printInputValue(arg: GraphQLInputField): string {
   if (arg.defaultValue) {
     const literal =
       arg.defaultValue.literal ??
-      astFromValue(arg.defaultValue.value, arg.type);
+      valueToLiteral(arg.defaultValue.value, arg.type);
     invariant(literal, 'Invalid default value');
     argDecl += ` = ${print(literal)}`;
   }