Skip to main content

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

OptionDescription
-d dirOutput directory for compiled .class files
-cp pathClasspath for dependencies
-source verSource compatibility version (e.g., -source 11)
-target verTarget bytecode version
-XlintEnable all recommended warnings
-encoding UTF-8Source 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
tip

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

  1. Separate source and output: Always use -d to send class files to a dedicated output directory.
  2. Use @file for source lists: Avoids command-line length limits with many source files.
  3. Build classpath dynamically: Loop over the lib directory to include all JARs.
  4. Set -encoding UTF-8: Prevents encoding-related compilation errors across different systems.
  5. 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.