GraphQL schema parser in Java

1. GraphQL Schema AST Classes

// GraphQLSchema.java
import java.util.*;
public class GraphQLSchema {
private final List<TypeDefinition> typeDefinitions;
private final List<DirectiveDefinition> directiveDefinitions;
private final SchemaDefinition schemaDefinition;
public GraphQLSchema(SchemaDefinition schemaDefinition, 
List<TypeDefinition> typeDefinitions,
List<DirectiveDefinition> directiveDefinitions) {
this.schemaDefinition = schemaDefinition;
this.typeDefinitions = typeDefinitions != null ? typeDefinitions : new ArrayList<>();
this.directiveDefinitions = directiveDefinitions != null ? directiveDefinitions : new ArrayList<>();
}
public Optional<SchemaDefinition> getSchemaDefinition() {
return Optional.ofNullable(schemaDefinition);
}
public List<TypeDefinition> getTypeDefinitions() {
return Collections.unmodifiableList(typeDefinitions);
}
public List<DirectiveDefinition> getDirectiveDefinitions() {
return Collections.unmodifiableList(directiveDefinitions);
}
public Optional<TypeDefinition> getTypeDefinition(String name) {
return typeDefinitions.stream()
.filter(type -> type.getName().equals(name))
.findFirst();
}
public List<ObjectTypeDefinition> getObjectTypeDefinitions() {
return typeDefinitions.stream()
.filter(type -> type instanceof ObjectTypeDefinition)
.map(type -> (ObjectTypeDefinition) type)
.toList();
}
}
// Base interfaces and classes
interface Definition {
String getName();
List<Directive> getDirectives();
}
abstract class NamedDefinition implements Definition {
protected final String name;
protected final String description;
protected final List<Directive> directives;
protected NamedDefinition(String name, String description, List<Directive> directives) {
this.name = name;
this.description = description;
this.directives = directives != null ? directives : new ArrayList<>();
}
@Override
public String getName() {
return name;
}
public String getDescription() {
return description;
}
@Override
public List<Directive> getDirectives() {
return Collections.unmodifiableList(directives);
}
}
interface Type {
String getName();
boolean isNonNull();
boolean isList();
Type getBaseType();
}

2. Type Definitions

// Type Definitions
class SchemaDefinition extends NamedDefinition {
private final List<OperationTypeDefinition> operationTypeDefinitions;
public SchemaDefinition(List<OperationTypeDefinition> operationTypeDefinitions,
List<Directive> directives) {
super("Schema", null, directives);
this.operationTypeDefinitions = operationTypeDefinitions != null ? 
operationTypeDefinitions : new ArrayList<>();
}
public List<OperationTypeDefinition> getOperationTypeDefinitions() {
return Collections.unmodifiableList(operationTypeDefinitions);
}
}
class OperationTypeDefinition {
private final String operation;
private final String typeName;
public OperationTypeDefinition(String operation, String typeName) {
this.operation = operation;
this.typeName = typeName;
}
public String getOperation() {
return operation;
}
public String getTypeName() {
return typeName;
}
}
interface TypeDefinition extends Definition {}
class ObjectTypeDefinition extends NamedDefinition implements TypeDefinition {
private final List<FieldDefinition> fieldDefinitions;
private final List<String> implementsInterfaces;
public ObjectTypeDefinition(String name, String description, 
List<FieldDefinition> fieldDefinitions,
List<String> implementsInterfaces,
List<Directive> directives) {
super(name, description, directives);
this.fieldDefinitions = fieldDefinitions != null ? fieldDefinitions : new ArrayList<>();
this.implementsInterfaces = implementsInterfaces != null ? implementsInterfaces : new ArrayList<>();
}
public List<FieldDefinition> getFieldDefinitions() {
return Collections.unmodifiableList(fieldDefinitions);
}
public List<String> getImplementsInterfaces() {
return Collections.unmodifiableList(implementsInterfaces);
}
}
class InterfaceTypeDefinition extends NamedDefinition implements TypeDefinition {
private final List<FieldDefinition> fieldDefinitions;
public InterfaceTypeDefinition(String name, String description,
List<FieldDefinition> fieldDefinitions,
List<Directive> directives) {
super(name, description, directives);
this.fieldDefinitions = fieldDefinitions != null ? fieldDefinitions : new ArrayList<>();
}
public List<FieldDefinition> getFieldDefinitions() {
return Collections.unmodifiableList(fieldDefinitions);
}
}
class EnumTypeDefinition extends NamedDefinition implements TypeDefinition {
private final List<EnumValueDefinition> enumValueDefinitions;
public EnumTypeDefinition(String name, String description,
List<EnumValueDefinition> enumValueDefinitions,
List<Directive> directives) {
super(name, description, directives);
this.enumValueDefinitions = enumValueDefinitions != null ? enumValueDefinitions : new ArrayList<>();
}
public List<EnumValueDefinition> getEnumValueDefinitions() {
return Collections.unmodifiableList(enumValueDefinitions);
}
}
class ScalarTypeDefinition extends NamedDefinition implements TypeDefinition {
public ScalarTypeDefinition(String name, String description, List<Directive> directives) {
super(name, description, directives);
}
}
class InputObjectTypeDefinition extends NamedDefinition implements TypeDefinition {
private final List<InputValueDefinition> inputFieldDefinitions;
public InputObjectTypeDefinition(String name, String description,
List<InputValueDefinition> inputFieldDefinitions,
List<Directive> directives) {
super(name, description, directives);
this.inputFieldDefinitions = inputFieldDefinitions != null ? inputFieldDefinitions : new ArrayList<>();
}
public List<InputValueDefinition> getInputFieldDefinitions() {
return Collections.unmodifiableList(inputFieldDefinitions);
}
}
class UnionTypeDefinition extends NamedDefinition implements TypeDefinition {
private final List<String> memberTypes;
public UnionTypeDefinition(String name, String description,
List<String> memberTypes,
List<Directive> directives) {
super(name, description, directives);
this.memberTypes = memberTypes != null ? memberTypes : new ArrayList<>();
}
public List<String> getMemberTypes() {
return Collections.unmodifiableList(memberTypes);
}
}

3. Field and Value Definitions

// Field and Value Definitions
class FieldDefinition {
private final String name;
private final String description;
private final Type type;
private final List<InputValueDefinition> arguments;
private final List<Directive> directives;
public FieldDefinition(String name, String description, Type type,
List<InputValueDefinition> arguments,
List<Directive> directives) {
this.name = name;
this.description = description;
this.type = type;
this.arguments = arguments != null ? arguments : new ArrayList<>();
this.directives = directives != null ? directives : new ArrayList<>();
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Type getType() {
return type;
}
public List<InputValueDefinition> getArguments() {
return Collections.unmodifiableList(arguments);
}
public List<Directive> getDirectives() {
return Collections.unmodifiableList(directives);
}
}
class InputValueDefinition {
private final String name;
private final String description;
private final Type type;
private final Value defaultValue;
private final List<Directive> directives;
public InputValueDefinition(String name, String description, Type type,
Value defaultValue, List<Directive> directives) {
this.name = name;
this.description = description;
this.type = type;
this.defaultValue = defaultValue;
this.directives = directives != null ? directives : new ArrayList<>();
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Type getType() {
return type;
}
public Optional<Value> getDefaultValue() {
return Optional.ofNullable(defaultValue);
}
public List<Directive> getDirectives() {
return Collections.unmodifiableList(directives);
}
}
class EnumValueDefinition extends NamedDefinition {
public EnumValueDefinition(String name, String description, List<Directive> directives) {
super(name, description, directives);
}
}

4. Type System

// Type System
class NamedType implements Type {
private final String name;
public NamedType(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isNonNull() {
return false;
}
@Override
public boolean isList() {
return false;
}
@Override
public Type getBaseType() {
return this;
}
@Override
public String toString() {
return name;
}
}
class NonNullType implements Type {
private final Type wrappedType;
public NonNullType(Type wrappedType) {
this.wrappedType = wrappedType;
}
@Override
public String getName() {
return wrappedType.getName() + "!";
}
@Override
public boolean isNonNull() {
return true;
}
@Override
public boolean isList() {
return false;
}
@Override
public Type getBaseType() {
return wrappedType.getBaseType();
}
@Override
public String toString() {
return wrappedType.toString() + "!";
}
}
class ListType implements Type {
private final Type wrappedType;
public ListType(Type wrappedType) {
this.wrappedType = wrappedType;
}
@Override
public String getName() {
return "[" + wrappedType.getName() + "]";
}
@Override
public boolean isNonNull() {
return false;
}
@Override
public boolean isList() {
return true;
}
@Override
public Type getBaseType() {
return wrappedType.getBaseType();
}
@Override
public String toString() {
return "[" + wrappedType.toString() + "]";
}
}

5. Directives and Values

// Directives and Values
class DirectiveDefinition extends NamedDefinition {
private final List<InputValueDefinition> arguments;
private final List<String> locations;
public DirectiveDefinition(String name, String description,
List<InputValueDefinition> arguments,
List<String> locations,
List<Directive> directives) {
super(name, description, directives);
this.arguments = arguments != null ? arguments : new ArrayList<>();
this.locations = locations != null ? locations : new ArrayList<>();
}
public List<InputValueDefinition> getArguments() {
return Collections.unmodifiableList(arguments);
}
public List<String> getLocations() {
return Collections.unmodifiableList(locations);
}
}
class Directive {
private final String name;
private final List<Argument> arguments;
public Directive(String name, List<Argument> arguments) {
this.name = name;
this.arguments = arguments != null ? arguments : new ArrayList<>();
}
public String getName() {
return name;
}
public List<Argument> getArguments() {
return Collections.unmodifiableList(arguments);
}
}
class Argument {
private final String name;
private final Value value;
public Argument(String name, Value value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public Value getValue() {
return value;
}
}
interface Value {
Object getValue();
}
class StringValue implements Value {
private final String value;
public StringValue(String value) {
this.value = value;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
return "\"" + value.replace("\"", "\\\"") + "\"";
}
}
class IntValue implements Value {
private final int value;
public IntValue(int value) {
this.value = value;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}
class BooleanValue implements Value {
private final boolean value;
public BooleanValue(boolean value) {
this.value = value;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}
class EnumValue implements Value {
private final String value;
public EnumValue(String value) {
this.value = value;
}
@Override
public Object getValue() {
return value;
}
@Override
public String toString() {
return value;
}
}
class ListValue implements Value {
private final List<Value> values;
public ListValue(List<Value> values) {
this.values = values != null ? values : new ArrayList<>();
}
@Override
public Object getValue() {
return values.stream().map(Value::getValue).toList();
}
@Override
public String toString() {
return "[" + values.stream().map(Object::toString).collect(java.util.stream.Collectors.joining(", ")) + "]";
}
}
class ObjectValue implements Value {
private final List<Argument> fields;
public ObjectValue(List<Argument> fields) {
this.fields = fields != null ? fields : new ArrayList<>();
}
@Override
public Object getValue() {
Map<String, Object> result = new HashMap<>();
for (Argument field : fields) {
result.put(field.getName(), field.getValue().getValue());
}
return result;
}
@Override
public String toString() {
return "{" + fields.stream()
.map(f -> f.getName() + ": " + f.getValue())
.collect(java.util.stream.Collectors.joining(", ")) + "}";
}
}

6. GraphQL Schema Parser

// GraphQLSchemaParser.java
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class GraphQLSchemaParser {
private String input;
private int position;
private final int length;
private static final Pattern WHITESPACE = Pattern.compile("\\s");
private static final Pattern NAME_PATTERN = Pattern.compile("[_a-zA-Z][_a-zA-Z0-9]*");
public GraphQLSchemaParser(String input) {
this.input = input;
this.position = 0;
this.length = input.length();
}
public GraphQLSchema parse() {
List<TypeDefinition> typeDefinitions = new ArrayList<>();
List<DirectiveDefinition> directiveDefinitions = new ArrayList<>();
SchemaDefinition schemaDefinition = null;
skipWhitespaceAndComments();
while (position < length) {
if (peekKeyword("schema")) {
schemaDefinition = parseSchemaDefinition();
} else if (peekKeyword("type")) {
typeDefinitions.add(parseObjectTypeDefinition());
} else if (peekKeyword("interface")) {
typeDefinitions.add(parseInterfaceTypeDefinition());
} else if (peekKeyword("enum")) {
typeDefinitions.add(parseEnumTypeDefinition());
} else if (peekKeyword("scalar")) {
typeDefinitions.add(parseScalarTypeDefinition());
} else if (peekKeyword("input")) {
typeDefinitions.add(parseInputObjectTypeDefinition());
} else if (peekKeyword("union")) {
typeDefinitions.add(parseUnionTypeDefinition());
} else if (peekKeyword("directive")) {
directiveDefinitions.add(parseDirectiveDefinition());
} else {
throw new GraphQLParseException("Unexpected token at position " + position);
}
skipWhitespaceAndComments();
}
return new GraphQLSchema(schemaDefinition, typeDefinitions, directiveDefinitions);
}
private SchemaDefinition parseSchemaDefinition() {
expectKeyword("schema");
List<Directive> directives = parseDirectives();
expect("{");
List<OperationTypeDefinition> operationTypes = new ArrayList<>();
while (!peek("}")) {
String operation = parseName();
expect(":");
String typeName = parseName();
operationTypes.add(new OperationTypeDefinition(operation, typeName));
skipWhitespaceAndComments();
}
expect("}");
return new SchemaDefinition(operationTypes, directives);
}
private ObjectTypeDefinition parseObjectTypeDefinition() {
expectKeyword("type");
String name = parseName();
String description = null; // Could parse description from comments
List<String> implementsInterfaces = new ArrayList<>();
if (peekKeyword("implements")) {
expectKeyword("implements");
implementsInterfaces.add(parseName());
while (peek("&")) {
expect("&");
implementsInterfaces.add(parseName());
}
}
List<Directive> directives = parseDirectives();
List<FieldDefinition> fields = parseFieldsDefinition();
return new ObjectTypeDefinition(name, description, fields, implementsInterfaces, directives);
}
private InterfaceTypeDefinition parseInterfaceTypeDefinition() {
expectKeyword("interface");
String name = parseName();
String description = null;
List<Directive> directives = parseDirectives();
List<FieldDefinition> fields = parseFieldsDefinition();
return new InterfaceTypeDefinition(name, description, fields, directives);
}
private List<FieldDefinition> parseFieldsDefinition() {
expect("{");
List<FieldDefinition> fields = new ArrayList<>();
while (!peek("}")) {
fields.add(parseFieldDefinition());
skipWhitespaceAndComments();
}
expect("}");
return fields;
}
private FieldDefinition parseFieldDefinition() {
String name = parseName();
String description = null;
List<InputValueDefinition> arguments = new ArrayList<>();
if (peek("(")) {
arguments = parseArgumentsDefinition();
}
expect(":");
Type type = parseType();
List<Directive> directives = parseDirectives();
return new FieldDefinition(name, description, type, arguments, directives);
}
private List<InputValueDefinition> parseArgumentsDefinition() {
expect("(");
List<InputValueDefinition> arguments = new ArrayList<>();
while (!peek(")")) {
arguments.add(parseInputValueDefinition());
skipWhitespaceAndComments();
}
expect(")");
return arguments;
}
private InputValueDefinition parseInputValueDefinition() {
String name = parseName();
String description = null;
expect(":");
Type type = parseType();
Value defaultValue = null;
if (peek("=")) {
expect("=");
defaultValue = parseValue();
}
List<Directive> directives = parseDirectives();
return new InputValueDefinition(name, description, type, defaultValue, directives);
}
private Type parseType() {
Type type;
if (peek("[")) {
expect("[");
type = new ListType(parseType());
expect("]");
} else {
type = new NamedType(parseName());
}
if (peek("!")) {
expect("!");
type = new NonNullType(type);
}
return type;
}
private List<Directive> parseDirectives() {
List<Directive> directives = new ArrayList<>();
while (peek("@")) {
directives.add(parseDirective());
skipWhitespaceAndComments();
}
return directives;
}
private Directive parseDirective() {
expect("@");
String name = parseName();
List<Argument> arguments = new ArrayList<>();
if (peek("(")) {
arguments = parseArguments();
}
return new Directive(name, arguments);
}
private List<Argument> parseArguments() {
expect("(");
List<Argument> arguments = new ArrayList<>();
while (!peek(")")) {
String name = parseName();
expect(":");
Value value = parseValue();
arguments.add(new Argument(name, value));
skipWhitespaceAndComments();
}
expect(")");
return arguments;
}
private Value parseValue() {
skipWhitespaceAndComments();
char current = input.charAt(position);
if (current == '"') {
return parseStringValue();
} else if (Character.isDigit(current) || current == '-') {
return parseIntValue();
} else if (peekKeyword("true") || peekKeyword("false")) {
return parseBooleanValue();
} else if (peek("[")) {
return parseListValue();
} else if (peek("{")) {
return parseObjectValue();
} else {
// Enum value or variable (simplified)
return new EnumValue(parseName());
}
}
private StringValue parseStringValue() {
expect("\"");
StringBuilder sb = new StringBuilder();
while (position < length && input.charAt(position) != '"') {
if (input.charAt(position) == '\\') {
position++;
// Handle escape sequences (simplified)
}
sb.append(input.charAt(position));
position++;
}
expect("\"");
return new StringValue(sb.toString());
}
private IntValue parseIntValue() {
StringBuilder sb = new StringBuilder();
while (position < length && Character.isDigit(input.charAt(position))) {
sb.append(input.charAt(position));
position++;
}
return new IntValue(Integer.parseInt(sb.toString()));
}
private BooleanValue parseBooleanValue() {
if (peekKeyword("true")) {
expectKeyword("true");
return new BooleanValue(true);
} else {
expectKeyword("false");
return new BooleanValue(false);
}
}
private ListValue parseListValue() {
expect("[");
List<Value> values = new ArrayList<>();
while (!peek("]")) {
values.add(parseValue());
skipWhitespaceAndComments();
}
expect("]");
return new ListValue(values);
}
private ObjectValue parseObjectValue() {
expect("{");
List<Argument> fields = new ArrayList<>();
while (!peek("}")) {
String name = parseName();
expect(":");
Value value = parseValue();
fields.add(new Argument(name, value));
skipWhitespaceAndComments();
}
expect("}");
return new ObjectValue(fields);
}
// Helper parsing methods for other type definitions...
private EnumTypeDefinition parseEnumTypeDefinition() {
expectKeyword("enum");
String name = parseName();
String description = null;
List<Directive> directives = parseDirectives();
expect("{");
List<EnumValueDefinition> values = new ArrayList<>();
while (!peek("}")) {
String valueName = parseName();
List<Directive> valueDirectives = parseDirectives();
values.add(new EnumValueDefinition(valueName, null, valueDirectives));
skipWhitespaceAndComments();
}
expect("}");
return new EnumTypeDefinition(name, description, values, directives);
}
private ScalarTypeDefinition parseScalarTypeDefinition() {
expectKeyword("scalar");
String name = parseName();
String description = null;
List<Directive> directives = parseDirectives();
return new ScalarTypeDefinition(name, description, directives);
}
private InputObjectTypeDefinition parseInputObjectTypeDefinition() {
expectKeyword("input");
String name = parseName();
String description = null;
List<Directive> directives = parseDirectives();
expect("{");
List<InputValueDefinition> fields = new ArrayList<>();
while (!peek("}")) {
fields.add(parseInputValueDefinition());
skipWhitespaceAndComments();
}
expect("}");
return new InputObjectTypeDefinition(name, description, fields, directives);
}
private UnionTypeDefinition parseUnionTypeDefinition() {
expectKeyword("union");
String name = parseName();
String description = null;
List<Directive> directives = parseDirectives();
expect("=");
List<String> members = new ArrayList<>();
members.add(parseName());
while (peek("|")) {
expect("|");
members.add(parseName());
}
return new UnionTypeDefinition(name, description, members, directives);
}
private DirectiveDefinition parseDirectiveDefinition() {
expectKeyword("directive");
expect("@");
String name = parseName();
String description = null;
List<InputValueDefinition> arguments = new ArrayList<>();
if (peek("(")) {
arguments = parseArgumentsDefinition();
}
expectKeyword("on");
List<String> locations = new ArrayList<>();
locations.add(parseName());
while (peek("|")) {
expect("|");
locations.add(parseName());
}
return new DirectiveDefinition(name, description, arguments, locations, null);
}
// Utility parsing methods
private String parseName() {
skipWhitespaceAndComments();
StringBuilder sb = new StringBuilder();
while (position < length && 
(Character.isLetterOrDigit(input.charAt(position)) || 
input.charAt(position) == '_')) {
sb.append(input.charAt(position));
position++;
}
String name = sb.toString();
if (!NAME_PATTERN.matcher(name).matches()) {
throw new GraphQLParseException("Invalid name: " + name);
}
return name;
}
private void expect(String expected) {
skipWhitespaceAndComments();
if (position + expected.length() > length) {
throw new GraphQLParseException("Expected '" + expected + "' but reached end of input");
}
String actual = input.substring(position, position + expected.length());
if (!actual.equals(expected)) {
throw new GraphQLParseException("Expected '" + expected + "' but found '" + actual + "'");
}
position += expected.length();
skipWhitespaceAndComments();
}
private void expectKeyword(String keyword) {
skipWhitespaceAndComments();
// Check if we have the keyword followed by non-name character
if (!peekKeyword(keyword)) {
throw new GraphQLParseException("Expected keyword '" + keyword + "'");
}
position += keyword.length();
skipWhitespaceAndComments();
}
private boolean peek(String expected) {
skipWhitespaceAndComments();
if (position + expected.length() > length) {
return false;
}
return input.substring(position, position + expected.length()).equals(expected);
}
private boolean peekKeyword(String keyword) {
int savedPosition = position;
skipWhitespaceAndComments();
if (position + keyword.length() > length) {
position = savedPosition;
return false;
}
boolean matches = input.substring(position, position + keyword.length()).equals(keyword);
// Check that it's not part of a larger name
if (matches && position + keyword.length() < length) {
char nextChar = input.charAt(position + keyword.length());
if (Character.isLetterOrDigit(nextChar) || nextChar == '_') {
matches = false;
}
}
position = savedPosition;
return matches;
}
private void skipWhitespaceAndComments() {
while (position < length) {
char current = input.charAt(position);
if (WHITESPACE.matcher(String.valueOf(current)).matches()) {
position++;
} else if (current == '#') {
// Skip comment until end of line
while (position < length && input.charAt(position) != '\n') {
position++;
}
} else {
break;
}
}
}
}
class GraphQLParseException extends RuntimeException {
public GraphQLParseException(String message) {
super(message);
}
public GraphQLParseException(String message, Throwable cause) {
super(message, cause);
}
}

7. Usage Example

// Example usage
public class GraphQLParserExample {
public static void main(String[] args) {
String schemaString = """
type Query {
user(id: ID!): User
users: [User!]!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
input CreateUserInput {
name: String!
email: String!
}
enum Role {
ADMIN
USER
GUEST
}
directive @auth(role: Role!) on FIELD_DEFINITION
""";
try {
GraphQLSchemaParser parser = new GraphQLSchemaParser(schemaString);
GraphQLSchema schema = parser.parse();
// Print all type definitions
for (TypeDefinition typeDef : schema.getTypeDefinitions()) {
if (typeDef instanceof ObjectTypeDefinition) {
ObjectTypeDefinition objectType = (ObjectTypeDefinition) typeDef;
System.out.println("Type: " + objectType.getName());
for (FieldDefinition field : objectType.getFieldDefinitions()) {
System.out.println("  Field: " + field.getName() + ": " + field.getType());
}
}
}
} catch (GraphQLParseException e) {
System.err.println("Parse error: " + e.getMessage());
}
}
}

Key Features:

  1. Complete SDL Support: Parses all GraphQL schema definition types
  2. Type System: Handles named types, lists, and non-null types
  3. Directives: Supports directive definitions and applications
  4. Values: Parses various value types (strings, integers, booleans, enums, lists, objects)
  5. Error Handling: Comprehensive error reporting with position information
  6. AST Building: Constructs complete abstract syntax tree
  7. Extensible: Easy to extend with additional features

This parser provides a solid foundation for building GraphQL tools, validators, code generators, or schema management systems in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper