How to Compile a Java Project from a Batch Script (javac)
Automating Java compilation through Batch Script is essential for build workflows that need to run outside of heavyweight build tools like Maven or Gradle. Whether you are building a simple single-file utility, a multi-file project with package structure, or generating JAR archives, the javac compiler and jar tool can be orchestrated directly from a Batch Script for fast, lightweight, and transparent builds.
In this guide, we will explore how to compile Java projects from a Batch Script using javac, manage classpaths, create JAR files, and handle common build scenarios.
Prerequisites
Ensure the Java Development Kit (JDK) is installed and javac is on the system PATH:
@echo off
javac -version 2>nul
if %errorlevel% neq 0 (
echo [ERROR] JDK not found. Install JDK and add to PATH.
echo Download from: https://jdk.java.net/
pause
exit /b 1
)
Method 1: Compiling a Single Java File
@echo off
setlocal
set "source=src\HelloWorld.java"
set "output=bin"
if not exist "%source%" (
echo [ERROR] Source file not found: %source%
pause
exit /b 1
)
echo Compiling %source%...
if not exist "%output%" mkdir "%output%"
javac -d "%output%" "%source%"
if %errorlevel%==0 (
echo [SUCCESS] Compiled.
echo Running...
java -cp "%output%" HelloWorld
) else (
echo [ERROR] Compilation failed.
exit /b 1
)
pause
Key javac Options
| Option | Description |
|---|---|
-d dir | Output directory for compiled .class files |
-cp path | Classpath for dependencies |
-source ver | Source compatibility version (e.g., -source 11) |
-target ver | Target bytecode version |
-Xlint | Enable all recommended warnings |
-encoding UTF-8 | Source file encoding |
Method 2: Compiling a Multi-File Project
For projects with multiple source files and package structure:
@echo off
setlocal enabledelayedexpansion
set "src_dir=src"
set "out_dir=bin"
set "lib_dir=lib"
set "sources_file=%temp%\java_sources_%random%.txt"
echo =============================================
echo JAVA BUILD
echo =============================================
echo.
:: Verify source directory exists
if not exist "%src_dir%\" (
echo [ERROR] Source directory not found: %src_dir%
pause
exit /b 1
)
:: Clean output directory
if exist "%out_dir%" rmdir /s /q "%out_dir%"
mkdir "%out_dir%"
:: Collect all .java files
echo [1/2] Finding source files...
dir /s /b "%src_dir%\*.java" > "%sources_file%" 2>nul
set "file_count=0"
for /f %%C in ('type "%sources_file%" ^| find /c /v ""') do set "file_count=%%C"
if %file_count%==0 (
echo [ERROR] No .java files found in %src_dir%.
del "%sources_file%" 2>nul
exit /b 1
)
echo Found %file_count% source files.
:: Build classpath from lib directory
set "classpath="
if exist "%lib_dir%\" (
for %%J in ("%lib_dir%\*.jar") do (
if defined classpath (
set "classpath=!classpath!;%%J"
) else (
set "classpath=%%J"
)
)
)
:: Compile
echo [2/2] Compiling...
if defined classpath (
javac -d "%out_dir%" -cp "!classpath!" -encoding UTF-8 -Xlint @"%sources_file%"
) else (
javac -d "%out_dir%" -encoding UTF-8 -Xlint @"%sources_file%"
)
set "compile_result=!errorlevel!"
del "%sources_file%" 2>nul
if !compile_result!==0 (
echo.
echo [SUCCESS] Compiled %file_count% files to %out_dir%
) else (
echo.
echo [ERROR] Compilation failed.
exit /b 1
)
pause
The @file syntax tells javac to read the list of source files from a file. This avoids command-line length limits when compiling many files.
Method 3: Build and Create JAR
Compile the project and package it into an executable JAR:
@echo off
setlocal enabledelayedexpansion
set "app_name=MyApp"
set "main_class=com.example.Main"
set "src_dir=src"
set "out_dir=bin"
set "lib_dir=lib"
set "dist_dir=dist"
set "jar_file=%dist_dir%\%app_name%.jar"
set "sources_file=%temp%\java_sources_%random%.txt"
echo =============================================
echo BUILD AND PACKAGE
echo =============================================
echo.
:: Clean
if exist "%out_dir%" rmdir /s /q "%out_dir%"
if exist "%dist_dir%" rmdir /s /q "%dist_dir%"
mkdir "%out_dir%"
mkdir "%dist_dir%"
:: Collect sources
dir /s /b "%src_dir%\*.java" > "%sources_file%" 2>nul
set "file_count=0"
for /f %%C in ('type "%sources_file%" ^| find /c /v ""') do set "file_count=%%C"
if %file_count%==0 (
echo [ERROR] No .java files found in %src_dir%.
del "%sources_file%" 2>nul
exit /b 1
)
:: Build classpath
set "classpath="
if exist "%lib_dir%\" (
for %%J in ("%lib_dir%\*.jar") do (
if defined classpath (set "classpath=!classpath!;%%J") else (set "classpath=%%J")
)
)
:: Compile
echo [1/3] Compiling %file_count% files...
if defined classpath (
javac -d "%out_dir%" -cp "!classpath!" @"%sources_file%"
) else (
javac -d "%out_dir%" @"%sources_file%"
)
del "%sources_file%" 2>nul
if !errorlevel! neq 0 (
echo [FAIL] Compilation failed.
exit /b 1
)
echo Compiled.
:: Create manifest
echo [2/3] Creating manifest...
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "build_date=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%"
(
echo Manifest-Version: 1.0
echo Main-Class: %main_class%
echo Built-By: %USERNAME%
echo Build-Date: %build_date%
) > "%out_dir%\MANIFEST.MF"
:: Manifest files require a trailing newline
echo.>> "%out_dir%\MANIFEST.MF"
:: Create JAR
echo [3/3] Packaging JAR...
jar cfm "%jar_file%" "%out_dir%\MANIFEST.MF" -C "%out_dir%" .
if !errorlevel!==0 (
for %%F in ("%jar_file%") do set "size=%%~zF"
echo.
echo [SUCCESS] JAR created: %jar_file% (!size! bytes^)
echo Run with: java -jar %jar_file%
) else (
echo [FAIL] JAR creation failed.
exit /b 1
)
pause
Method 4: Full Build Pipeline with Tests
@echo off
setlocal enabledelayedexpansion
set "src_dir=src\main\java"
set "test_dir=src\test\java"
set "out_dir=build\classes"
set "test_out=build\test-classes"
set "lib_dir=lib"
echo =============================================
echo JAVA BUILD PIPELINE
echo =============================================
echo.
:: Clean
echo [1/4] Cleaning...
if exist "build" rmdir /s /q "build"
mkdir "%out_dir%"
mkdir "%test_out%"
echo Done.
:: Build classpath from lib/*.jar
set "classpath="
if exist "%lib_dir%\" (
for %%J in ("%lib_dir%\*.jar") do (
if defined classpath (set "classpath=!classpath!;%%J") else (set "classpath=%%J")
)
)
:: Compile main sources
echo [2/4] Compiling sources...
if not exist "%src_dir%\" (
echo [FAIL] Source directory not found: %src_dir%
exit /b 1
)
set "main_sources=%temp%\main_sources_%random%.txt"
dir /s /b "%src_dir%\*.java" > "!main_sources!" 2>nul
set "src_count=0"
for /f %%C in ('type "!main_sources!" ^| find /c /v ""') do set "src_count=%%C"
if !src_count!==0 (
echo [FAIL] No .java files found in %src_dir%.
del "!main_sources!" 2>nul
exit /b 1
)
if defined classpath (
javac -d "%out_dir%" -cp "!classpath!" -encoding UTF-8 @"!main_sources!" 2> build\compile_errors.txt
) else (
javac -d "%out_dir%" -encoding UTF-8 @"!main_sources!" 2> build\compile_errors.txt
)
if !errorlevel! neq 0 (
echo [FAIL] Compilation failed:
type build\compile_errors.txt
del "!main_sources!" 2>nul
exit /b 1
)
del "!main_sources!" 2>nul
echo Compiled !src_count! source files.
:: Compile tests
echo [3/4] Compiling tests...
if not exist "%test_dir%\" (
echo No test directory. Skipping.
goto run_tests
)
set "test_sources=%temp%\test_sources_%random%.txt"
dir /s /b "%test_dir%\*.java" > "!test_sources!" 2>nul
set "test_count=0"
for /f %%C in ('type "!test_sources!" ^| find /c /v ""') do set "test_count=%%C"
if !test_count!==0 (
echo No test files found. Skipping.
del "!test_sources!" 2>nul
goto run_tests
)
javac -d "%test_out%" -cp "%out_dir%;!classpath!" -encoding UTF-8 @"!test_sources!"
if !errorlevel! neq 0 (
echo [WARNING] Test compilation failed.
del "!test_sources!" 2>nul
goto run_tests
)
del "!test_sources!" 2>nul
echo Compiled !test_count! test files.
:: Run tests
:run_tests
echo [4/4] Running tests...
if not exist "%test_out%\" (
echo No compiled tests to run.
) else (
java -cp "%test_out%;%out_dir%;!classpath!" org.junit.runner.JUnitCore com.example.AllTests 2>nul
if !errorlevel!==0 (
echo Tests passed.
) else (
echo [WARNING] Tests failed or JUnit not available.
)
)
echo.
echo [BUILD COMPLETE]
pause
Method 5: Setting JAVA_HOME and Managing JDK Versions
@echo off
setlocal
:: Explicitly set JDK path
set "JAVA_HOME=C:\Program Files\Java\jdk-17"
if not exist "%JAVA_HOME%\bin\javac.exe" (
echo [ERROR] JDK not found at: %JAVA_HOME%
pause
exit /b 1
)
set "PATH=%JAVA_HOME%\bin;%PATH%"
echo Using JDK: %JAVA_HOME%
javac -version
echo.
:: Build
javac -d bin src\*.java
pause
For projects requiring a specific JDK version:
@echo off
setlocal
set "required_major=17"
:: Check current version
set "current="
for /f "tokens=2" %%V in ('javac -version 2^>^&1') do set "current=%%V"
if not defined current (
echo [ERROR] javac not found.
pause
exit /b 1
)
echo Current javac: %current%
:: Extract the major version number
for /f "tokens=1 delims=." %%M in ("%current%") do set "major=%%M"
if not "%major%"=="%required_major%" (
echo [ERROR] JDK %required_major% required. Found major version: %major%
pause
exit /b 1
)
echo [OK] JDK version matches (major: %major%^).
Common Mistakes
The Wrong Way: Not Setting the Output Directory
:: WRONG - Class files are created next to source files
javac src\com\example\Main.java
:: Creates Main.class in src\com\example\, mixing source and output
Output Concern:
Without -d, compiled .class files are placed alongside the .java source files, making the project messy and hard to package. Always use -d bin (or similar) to separate compiled output from source code.
The Wrong Way: Missing Classpath for Dependencies
:: WRONG - Fails if the project uses external JARs
javac -d bin src\com\example\Main.java
:: error: package org.apache.commons does not exist
If the project uses external libraries, you must include them in the classpath with -cp lib\*.jar or by building the classpath string explicitly.
Best Practices
- Separate source and output: Always use
-dto send class files to a dedicated output directory. - Use
@filefor source lists: Avoids command-line length limits with many source files. - Build classpath dynamically: Loop over the
libdirectory to include all JARs. - Set
-encoding UTF-8: Prevents encoding-related compilation errors across different systems. - Enable warnings with
-Xlint: Catches common issues at compile time.
Conclusion
Compiling Java projects from a Batch Script using javac provides a lightweight, transparent build process without the overhead of Maven or Gradle. By collecting source files with dir /s /b, building classpaths from a library directory, and packaging output into JAR files with jar, Batch scripts can handle everything from simple single-file programs to multi-package projects. For larger projects with complex dependency management, consider using Maven or Gradle, but for straightforward builds and educational purposes, javac orchestrated by a Batch script is fast and effective.