diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
index 3c8eaf739008f50343e2661fce05c83cdeda4a7d..7de0612fad2746db2160babbbf541b982bba39b7 100644
--- a/.azure/pipelines/jobs/default-build.yml
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -121,7 +121,7 @@ jobs:
     DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
     TeamName: AspNetCore
     ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}:
-      JAVA_HOME: $(Agent.BuildDirectory)\.tools\jdk
+      JAVA_HOME: $(Agent.BuildDirectory)\.tools\jdk\win-x64
     ${{ if or(ne(parameters.codeSign, true), ne(variables['System.TeamProject'], 'internal')) }}:
       _SignType: ''
     ${{ if and(eq(parameters.codeSign, true), eq(variables['System.TeamProject'], 'internal')) }}:
@@ -146,10 +146,7 @@ jobs:
         command: custom
         arguments: 'locals all -clear'
   - ${{ if and(eq(parameters.installJdk, 'true'), eq(parameters.agentOs, 'Windows')) }}:
-    - powershell: |
-        ./eng/scripts/InstallJdk.ps1 '11.0.1'
-        Write-Host "##vso[task.prependpath]$env:JAVA_HOME\bin"
-
+    - powershell: ./eng/scripts/InstallJdk.ps1
       displayName: Install JDK 11
     - ${{ if eq(parameters.isTestingJob, true) }}:
       - powershell: |
diff --git a/build.ps1 b/build.ps1
index 2cf4e51853d5e75dac7abe5b7fb89b2518ccaeed..75747822d19f17d6d56b8f7eebd622edeb0b64d3 100644
--- a/build.ps1
+++ b/build.ps1
@@ -306,7 +306,13 @@ $MSBuildArguments += "/p:TargetOsName=win"
 if (($All -or $BuildJava) -and -not $NoBuildJava) {
     $foundJdk = $false
     $javac = Get-Command javac -ErrorAction Ignore -CommandType Application
-    if ($env:JAVA_HOME) {
+    $localJdkPath = "$PSScriptRoot\.tools\jdk\win-x64\"
+    if (Test-Path "$localJdkPath\bin\javac.exe") {
+        $foundJdk = $true
+        Write-Host -f Magenta "Detected JDK in $localJdkPath (via local repo convention)"
+        $env:JAVA_HOME = $localJdkPath
+    }
+    elseif ($env:JAVA_HOME) {
         if (-not (Test-Path "${env:JAVA_HOME}\bin\javac.exe")) {
             Write-Error "The environment variable JAVA_HOME was set, but ${env:JAVA_HOME}\bin\javac.exe does not exist. Remove JAVA_HOME or update it to the correct location for the JDK. See https://www.bing.com/search?q=java_home for details."
         }
@@ -337,7 +343,7 @@ if (($All -or $BuildJava) -and -not $NoBuildJava) {
     }
 
     if (-not $foundJdk) {
-        Write-Error "Could not find the JDK. See $PSScriptRoot\docs\BuildFromSource.md for details on this requirement."
+        Write-Error "Could not find the JDK. Either run $PSScriptRoot\eng\scripts\InstallJdk.ps1 to install for this repo, or install the JDK globally on your machine (see $PSScriptRoot\docs\BuildFromSource.md for details)."
     }
 }
 
diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md
index 0524cd80f9b56682e373aaeb2ba359cc6361bb99..82ed16067d7483259ef95e9f0f9b389e81b39522 100644
--- a/docs/BuildFromSource.md
+++ b/docs/BuildFromSource.md
@@ -23,6 +23,10 @@ Building ASP.NET Core on Windows requires:
 * Java Development Kit 11 or newer. Either:
     * OpenJDK <https://jdk.java.net/>
     * Oracle's JDK <https://www.oracle.com/technetwork/java/javase/downloads/index.html>
+    * To install a version of the JDK that will only be used by this repo, run [eng/scripts/InstallJdk.ps1](/eng/scripts/InstallJdk.ps1)
+        ```ps1
+        PS> ./eng/scripts/InstalLJdk.ps1
+        ```
 
 ### macOS/Linux
 
diff --git a/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1 b/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1
index 5af8fc5dac08a7774e2d83d4b4da4273cfa832cd..8621112ac65724da0887a4bb9b9509c80c757d09 100644
--- a/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1
+++ b/eng/helix/content/mssql/InstallSqlServerLocalDB.ps1
@@ -3,6 +3,8 @@
     Installs SQL Server 2016 Express LocalDB on a machine.
 .DESCRIPTION
     This script installs Microsoft SQL Server 2016 Express LocalDB on a machine.
+.PARAMETER Force
+    Force the script to run the MSI, even it it appears LocalDB is installed.
 .LINK
     https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-2016-express-localdb?view=sql-server-2016
     https://docs.microsoft.com/en-us/sql/database-engine/install-windows/install-sql-server-from-the-command-prompt?view=sql-server-2016
diff --git a/eng/scripts/InstallJdk.ps1 b/eng/scripts/InstallJdk.ps1
index e56e768f4f7da89749c0b243b129dbdbb61f591c..3602b6e734ad8069e5f8e266166822443f31a52c 100644
--- a/eng/scripts/InstallJdk.ps1
+++ b/eng/scripts/InstallJdk.ps1
@@ -1,27 +1,52 @@
-
+<#
+.SYNOPSIS
+    Installs JDK into a folder in this repo.
+.DESCRIPTION
+    This script downloads an extracts the JDK.
+.PARAMETER JdkVersion
+    The version of the JDK to install. If not set, the default value is read from global.json
+.PARAMETER Force
+    Overwrite the existing installation
+#>
 param(
-    [Parameter(Mandatory = $true)]
-    $JdkVersion
+    [string]$JdkVersion,
+    [switch]$Force
 )
-
 $ErrorActionPreference = 'Stop'
 $ProgressPreference = 'SilentlyContinue' # Workaround PowerShell/PowerShell#2138
 
 Set-StrictMode -Version 1
 
-if (-not $env:JAVA_HOME) {
-    throw 'You must set the JAVA_HOME environment variable to the destination of the JDK.'
+$repoRoot = Resolve-Path "$PSScriptRoot\..\.."
+$installDir = "$repoRoot\.tools\jdk\win-x64\"
+$tempDir = "$repoRoot\obj"
+if (-not $JdkVersion) {
+    $globalJson = Get-Content "$repoRoot\global.json" | ConvertFrom-Json
+    $JdkVersion = $globalJson.tools.jdk
+}
+
+if (Test-Path $installDir) {
+    if ($Force) {
+        Remove-Item -Force -Recurse $installDir
+    }
+    else {
+        Write-Host "The JDK already installed to $installDir. Exiting without action. Call this script again with -Force to overwrite."
+        exit 0
+    }
 }
 
-$repoRoot = Resolve-Path "$PSScriptRoot/../.."
-$tempDir = "$repoRoot/obj"
+Remove-Item -Force -Recurse $tempDir -ErrorAction Ignore | out-null
 mkdir $tempDir -ea Ignore | out-null
+mkdir $installDir -ea Ignore | out-null
 Write-Host "Starting download of JDK ${JdkVersion}"
 Invoke-WebRequest -UseBasicParsing -Uri "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/java/jdk-${JdkVersion}_windows-x64_bin.zip" -Out "$tempDir/jdk.zip"
 Write-Host "Done downloading JDK ${JdkVersion}"
 Expand-Archive "$tempDir/jdk.zip" -d "$tempDir/jdk/"
 Write-Host "Expanded JDK to $tempDir"
-mkdir (split-path -parent $env:JAVA_HOME) -ea ignore | out-null
-Write-Host "Installing JDK to $env:JAVA_HOME"
-Move-Item "$tempDir/jdk/jdk-${jdkVersion}" $env:JAVA_HOME
-Write-Host "Done installing JDK to $env:JAVA_HOME"
+Write-Host "Installing JDK to $installDir"
+Move-Item "$tempDir/jdk/jdk-${JdkVersion}/*" $installDir
+Write-Host "Done installing JDK to $installDir"
+
+if ($env:TF_BUILD) {
+    Write-Host "##vso[task.prependpath]$installDir\bin"
+}
diff --git a/global.json b/global.json
index 445956802e9d448b266dab93f8f8b85c939bb594..20af51df715a0fafeeebf292a64d767a883036e4 100644
--- a/global.json
+++ b/global.json
@@ -2,6 +2,9 @@
   "sdk": {
     "version": "3.0.100-preview5-011568"
   },
+  "tools": {
+    "jdk": "11.0.3"
+  },
   "msbuild-sdks": {
     "Yarn.MSBuild": "1.15.2",
     "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19262.1"