--- /dev/null
+++ BUILD.bazel
@@ -0,0 +1,251 @@
+load(":javacc_bootstrap.bzl", "javacc_bootstrap")
+
+JAVAC_OPTS = [
+    "-XepAllErrorsAsWarnings",
+    "-Xep:ComparisonOutOfRange:OFF",
+    "-Xep:ChainingConstructorIgnoresParameter:OFF",
+]
+
+JAR_RESOURCES = glob(["src/main/resources/**/*"])
+
+JAVACC_PARSER_SRCS = glob([
+    "src/main/java/org/javacc/parser/**/*.java",
+    "src/main/java/org/javacc/utils/**/*.java",
+])
+
+JTREE_SRCS = glob([
+    "src/main/java/org/javacc/jjtree/**/*.java",
+])
+
+java_library(
+    name = "javacc_version",
+    srcs = ["src/main/java/org/javacc/Version.java"],
+)
+
+java_library(
+    name = "javacc_parser",
+    srcs = JAVACC_PARSER_SRCS + [
+        ":javacc_stage2_ConditionParser",
+        ":javacc_stage2_JavaCC",
+    ],
+    javacopts = JAVAC_OPTS,
+    deps = [":javacc_version"],
+)
+
+java_binary(
+    name = "javacc",
+    srcs = ["src/main/java/javacc.java"],
+    javacopts = JAVAC_OPTS,
+    main_class = "javacc",
+    resources = JAR_RESOURCES,
+    visibility = ["//visibility:public"],
+    deps = [":javacc_parser"],
+)
+
+java_binary(
+    name = "jjdoc",
+    srcs = ["src/main/java/jjdoc.java"] + glob([
+        "src/main/java/org/javacc/jjdoc/**/*.java",
+    ]),
+    javacopts = JAVAC_OPTS,
+    main_class = "jjdoc",
+    visibility = ["//visibility:public"],
+    deps = [":javacc_parser"],
+)
+
+java_binary(
+    name = "jjtree",
+    srcs = JTREE_SRCS + [
+        "src/main/java/jjtree.java",
+        ":javacc_stage2_JJTree",
+        ":jtree_stage2_JJTree_java",
+    ],
+    javacopts = JAVAC_OPTS,
+    main_class = "jjtree",
+    resources = JAR_RESOURCES,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":javacc_parser",
+        ":javacc_version",
+    ],
+)
+
+# javacc bootstrap {{{
+
+JAVACC_JJ_OUTS = [
+    "JavaCCParser.java",
+    "JavaCCParserConstants.java",
+    "JavaCCParserTokenManager.java",
+    "JavaCharStream.java",
+    "ParseException.java",
+    "TokenMgrError.java",
+]
+
+CONDITION_PARSER_JJ_OUTS = [
+    "ConditionParser.java",
+    "ConditionParserConstants.java",
+    "ConditionParserTokenManager.java",
+    "JavaCharStream.java",
+    "ParseException.java",
+    "Token.java",
+    "TokenMgrError.java",
+]
+
+javacc_bootstrap(
+    name = "javacc_stage1_JavaCC",
+    src = "src/main/javacc/JavaCC.jj",
+    outs = JAVACC_JJ_OUTS,
+    javacc = "//bootstrap:javacc_stage1",
+)
+
+javacc_bootstrap(
+    name = "javacc_stage1_ConditionParser",
+    src = "src/main/javacc/ConditionParser.jj",
+    outs = CONDITION_PARSER_JJ_OUTS,
+    javacc = "//bootstrap:javacc_stage1",
+)
+
+javacc_bootstrap(
+    name = "javacc_stage2_JavaCC",
+    src = "src/main/javacc/JavaCC.jj",
+    outs = JAVACC_JJ_OUTS,
+    javacc = ":javacc_stage2",
+)
+
+javacc_bootstrap(
+    name = "javacc_stage2_ConditionParser",
+    src = "src/main/javacc/ConditionParser.jj",
+    outs = CONDITION_PARSER_JJ_OUTS,
+    javacc = ":javacc_stage2",
+)
+
+java_binary(
+    name = "javacc_stage2",
+    srcs = JAVACC_PARSER_SRCS + [
+        "src/main/java/javacc.java",
+        ":javacc_stage1_ConditionParser",
+        ":javacc_stage1_JavaCC",
+    ],
+    javacopts = JAVAC_OPTS,
+    main_class = "javacc",
+    resources = JAR_RESOURCES,
+    deps = [":javacc_version"],
+)
+
+# }}}
+
+# jtree bootstrap {{{
+
+JJTREE_JJ_OUTS = [
+    "JavaCharStream.java",
+    "JJTreeParserConstants.java",
+    "JJTreeParser.java",
+    "JJTreeParserTokenManager.java",
+    "ParseException.java",
+    "TokenMgrError.java",
+]
+
+JJTREE_JJT_OUTS = [
+    "ASTBNFAssignment.java",
+    "ASTBNFChoice.java",
+    "ASTBNFLookahead.java",
+    "ASTBNFNonTerminal.java",
+    "ASTBNFOneOrMore.java",
+    "ASTBNFParenthesized.java",
+    "ASTBNFSequence.java",
+    "ASTBNFTryBlock.java",
+    "ASTBNFZeroOrMore.java",
+    "ASTBNFZeroOrOne.java",
+    "ASTCharDescriptor.java",
+    "ASTPrimaryExpression.java",
+    "ASTProductions.java",
+    "ASTRECharList.java",
+    "ASTREChoice.java",
+    "ASTREEOF.java",
+    "ASTRE.java",
+    "ASTRENamed.java",
+    "ASTREOneOrMore.java",
+    "ASTREParenthesized.java",
+    "ASTREReference.java",
+    "ASTRESequence.java",
+    "ASTRESpec.java",
+    "ASTREStringLiteral.java",
+    "ASTREZeroOrMore.java",
+    "ASTREZeroOrOne.java",
+    "ASTRRepetitionRange.java",
+    "ASTTokenDecls.java",
+    "JJTJJTreeParserState.java",
+    "JJTreeParserTreeConstants.java",
+    "Node.java",
+    "SimpleNode.java",
+]
+
+javacc_bootstrap(
+    name = "jtree_stage1_JJTree",
+    src = "src/main/jjtree/JJTree.jjt",
+    outs = ["JJTree.jj"] + JJTREE_JJT_OUTS,
+    javacc = "//bootstrap:jjtree_stage1",
+)
+
+filegroup(
+    name = "jtree_stage1_JJTree_java",
+    srcs = [":jtree_stage1_JJTree"],
+    output_group = "java_files",
+)
+
+filegroup(
+    name = "jtree_stage1_JJTree_jj",
+    srcs = [":jtree_stage1_JJTree"],
+    output_group = "jj_files",
+)
+
+javacc_bootstrap(
+    name = "javacc_stage1_JJTree",
+    src = ":jtree_stage1_JJTree_jj",
+    outs = JJTREE_JJ_OUTS,
+    javacc = ":javacc",
+)
+
+java_binary(
+    name = "jjtree_stage2",
+    srcs = JTREE_SRCS + [
+        "src/main/java/jjtree.java",
+        ":javacc_stage1_JJTree",
+        ":jtree_stage1_JJTree_java",
+    ],
+    javacopts = JAVAC_OPTS,
+    main_class = "jjtree",
+    resources = JAR_RESOURCES,
+    deps = [
+        ":javacc_parser",
+        ":javacc_version",
+    ],
+)
+
+javacc_bootstrap(
+    name = "jtree_stage2_JJTree",
+    src = "src/main/jjtree/JJTree.jjt",
+    outs = ["JJTree.jj"] + JJTREE_JJT_OUTS,
+    javacc = ":jjtree_stage2",
+)
+
+filegroup(
+    name = "jtree_stage2_JJTree_java",
+    srcs = [":jtree_stage2_JJTree"],
+    output_group = "java_files",
+)
+
+filegroup(
+    name = "jtree_stage2_JJTree_jj",
+    srcs = [":jtree_stage2_JJTree"],
+    output_group = "jj_files",
+)
+
+javacc_bootstrap(
+    name = "javacc_stage2_JJTree",
+    src = ":jtree_stage2_JJTree_jj",
+    outs = JJTREE_JJ_OUTS,
+    javacc = ":javacc",
+)
+
+# }}}

--- /dev/null
+++ bootstrap/BUILD.bazel
@@ -0,0 +1,18 @@
+java_import(
+    name = "javacc_jar",
+    jars = ["javacc.jar"],
+)
+
+java_binary(
+    name = "javacc_stage1",
+    main_class = "javacc",
+    visibility = ["//:__subpackages__"],
+    runtime_deps = [":javacc_jar"],
+)
+
+java_binary(
+    name = "jjtree_stage1",
+    main_class = "jjtree",
+    visibility = ["//:__subpackages__"],
+    runtime_deps = [":javacc_jar"],
+)

--- /dev/null
+++ javacc_bootstrap.bzl
@@ -0,0 +1,42 @@
+def _javacc_bootstrap(ctx):
+    outs = []
+    java_outs = []
+    jj_outs = []
+    for out_basename in ctx.attr.outs:
+        out = ctx.actions.declare_file(ctx.attr.name + "/" + out_basename)
+        outs.append(out)
+        if out_basename.endswith(".java"):
+            java_outs.append(out)
+        if out_basename.endswith(".jj"):
+            jj_outs.append(out)
+    ctx.actions.run(
+        outputs = outs,
+        inputs = ctx.files.src,
+        executable = ctx.executable.javacc,
+        arguments = [
+            "-OUTPUT_DIRECTORY:" + outs[0].dirname,
+            ctx.file.src.path,
+        ],
+        mnemonic = "JavaccBootstrap",
+    )
+    return [
+        DefaultInfo(files = depset(outs)),
+        OutputGroupInfo(
+            jj_files = depset(jj_outs),
+            java_files = depset(java_outs),
+        ),
+    ]
+
+javacc_bootstrap = rule(
+    implementation = _javacc_bootstrap,
+    attrs = {
+        "javacc": attr.label(
+            executable = True,
+            cfg = "exec",
+        ),
+        "src": attr.label(
+            allow_single_file = True,
+        ),
+        "outs": attr.string_list(),
+    },
+)
