diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts
index 2015c1007c..0743db122c 100644
--- a/src/commons/application/ApplicationTypes.ts
+++ b/src/commons/application/ApplicationTypes.ts
@@ -219,7 +219,7 @@ export const javaLanguages: SALanguage[] = [
     variant: Variant.DEFAULT,
     displayName: 'Java',
     mainLanguage: SupportedLanguage.JAVA,
-    supports: {}
+    supports: { cseMachine: true }
   }
 ];
 export const cLanguages: SALanguage[] = [
diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx
index aad0e84e19..11759c56ca 100644
--- a/src/commons/controlBar/ControlBarChapterSelect.tsx
+++ b/src/commons/controlBar/ControlBarChapterSelect.tsx
@@ -9,6 +9,7 @@ import {
   fullJSLanguage,
   fullTSLanguage,
   htmlLanguage,
+  javaLanguages,
   pyLanguages,
   SALanguage,
   schemeLanguages,
@@ -87,7 +88,8 @@ export const ControlBarChapterSelect: React.FC<ControlBarChapterSelectProps> = (
     // See https://github.com/source-academy/frontend/pull/2460#issuecomment-1528759912
     ...(Constants.playgroundOnly ? [fullJSLanguage, fullTSLanguage, htmlLanguage] : []),
     ...schemeLanguages,
-    ...pyLanguages
+    ...pyLanguages,
+    ...javaLanguages
   ];
 
   return (
diff --git a/src/commons/sagas/PlaygroundSaga.ts b/src/commons/sagas/PlaygroundSaga.ts
index f8e1409145..e7acf4fe01 100644
--- a/src/commons/sagas/PlaygroundSaga.ts
+++ b/src/commons/sagas/PlaygroundSaga.ts
@@ -5,6 +5,7 @@ import qs from 'query-string';
 import { SagaIterator } from 'redux-saga';
 import { call, delay, put, race, select } from 'redux-saga/effects';
 import CseMachine from 'src/features/cseMachine/CseMachine';
+import { CseMachine as JavaCseMachine } from 'src/features/cseMachine/java/CseMachine';
 
 import {
   changeQueryString,
@@ -100,12 +101,17 @@ export default function* PlaygroundSaga(): SagaIterator {
       if (newId !== SideContentType.cseMachine) {
         yield put(toggleUsingCse(false, workspaceLocation));
         yield call([CseMachine, CseMachine.clearCse]);
+        yield call([JavaCseMachine, JavaCseMachine.clearCse]);
         yield put(updateCurrentStep(-1, workspaceLocation));
         yield put(updateStepsTotal(0, workspaceLocation));
         yield put(toggleUpdateCse(true, workspaceLocation));
         yield put(setEditorHighlightedLines(workspaceLocation, 0, []));
       }
 
+      if (playgroundSourceChapter === Chapter.FULL_JAVA && newId === SideContentType.cseMachine) {
+        yield put(toggleUsingCse(true, workspaceLocation));
+      }
+
       if (
         isSourceLanguage(playgroundSourceChapter) &&
         (newId === SideContentType.substVisualizer || newId === SideContentType.cseMachine)
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
index 6dc00046f8..0dae58611d 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
@@ -250,6 +250,7 @@ export function* evalCode(
   let lastDebuggerResult = yield select(
     (state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult
   );
+  const isUsingCse = yield select((state: OverallState) => state.workspaces['playground'].usingCse);
 
   // Handles `console.log` statements in fullJS
   const detachConsole: () => void =
@@ -266,7 +267,7 @@ export function* evalCode(
         : isC
         ? call(cCompileAndRun, entrypointCode, context)
         : isJava
-        ? call(javaRun, entrypointCode, context)
+        ? call(javaRun, entrypointCode, context, currentStep, isUsingCse)
         : call(
             runFilesInContext,
             isFolderModeEnabled
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts
index 7a0305091b..788e813ebf 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/updateInspector.ts
@@ -1,24 +1,41 @@
+import { Chapter } from 'js-slang/dist/types';
 import { SagaIterator } from 'redux-saga';
 import { put, select } from 'redux-saga/effects';
 
 import { OverallState } from '../../../application/ApplicationTypes';
 import { actions } from '../../../utils/ActionsHelper';
+import { visualizeJavaCseMachine } from '../../../utils/JavaHelper';
 import { visualizeCseMachine } from '../../../utils/JsSlangHelper';
 import { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
 
 export function* updateInspector(workspaceLocation: WorkspaceLocation): SagaIterator {
   try {
-    const lastDebuggerResult = yield select(
-      (state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult
-    );
-    const row = lastDebuggerResult.context.runtime.nodes[0].loc.start.line - 1;
-    // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
-    yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
-    // We highlight only one row to show the current line
-    // If we highlight from start to end, the whole program block will be highlighted at the start
-    // since the first node is the program node
-    yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[row, row]]));
-    visualizeCseMachine(lastDebuggerResult);
+    const [lastDebuggerResult, chapter] = yield select((state: OverallState) => [
+      state.workspaces[workspaceLocation].lastDebuggerResult,
+      state.workspaces[workspaceLocation].context.chapter
+    ]);
+    if (chapter === Chapter.FULL_JAVA) {
+      const controlItem = lastDebuggerResult.context.control.peek();
+      let start = -1;
+      let end = -1;
+      if (controlItem?.srcNode?.location) {
+        const node = controlItem.srcNode;
+        start = node.location.startLine - 1;
+        end = node.location.endLine ? node.location.endLine - 1 : start;
+      }
+      yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
+      yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[start, end]]));
+      visualizeJavaCseMachine(lastDebuggerResult);
+    } else {
+      const row = lastDebuggerResult.context.runtime.nodes[0].loc.start.line - 1;
+      // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
+      yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
+      // We highlight only one row to show the current line
+      // If we highlight from start to end, the whole program block will be highlighted at the start
+      // since the first node is the program node
+      yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, [[row, row]]));
+      visualizeCseMachine(lastDebuggerResult);
+    }
   } catch (e) {
     // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
     yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
diff --git a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap
index 3ded2a0fb5..f33e89daec 100644
--- a/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap
+++ b/src/commons/sideContent/__tests__/__snapshots__/SideContentCseMachine.tsx.snap
@@ -352,17 +352,19 @@ exports[`CSE Machine component renders correctly 1`] = `
     data-testid="cse-machine-default-text"
     id="cse-machine-default-text"
   >
-    The CSE machine generates control, stash and environment model diagrams following a notation introduced in
-     
-    <a
-      href="https://sourceacademy.org/sicpjs/3.2"
-      rel="noopener noreferrer"
-      target="_blank"
-    >
-      <i>
-        Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2
-      </i>
-    </a>
+    <span>
+      The CSE machine generates control, stash and environment model diagrams following a notation introduced in
+       
+      <a
+        href="https://sourceacademy.org/sicpjs/3.2"
+        rel="noopener noreferrer"
+        target="_blank"
+      >
+        <i>
+          Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2
+        </i>
+      </a>
+    </span>
     .
     <br />
     <br />
diff --git a/src/commons/sideContent/content/SideContentCseMachine.tsx b/src/commons/sideContent/content/SideContentCseMachine.tsx
index 009c2aec85..fe6adf3048 100644
--- a/src/commons/sideContent/content/SideContentCseMachine.tsx
+++ b/src/commons/sideContent/content/SideContentCseMachine.tsx
@@ -10,6 +10,7 @@ import {
 import { IconNames } from '@blueprintjs/icons';
 import { Tooltip2 } from '@blueprintjs/popover2';
 import classNames from 'classnames';
+import { Chapter } from 'js-slang/dist/types';
 import { debounce } from 'lodash';
 import React from 'react';
 import { HotKeys } from 'react-hotkeys';
@@ -20,6 +21,7 @@ import type { PlaygroundWorkspaceState } from 'src/commons/workspace/WorkspaceTy
 import CseMachine from 'src/features/cseMachine/CseMachine';
 import { CseAnimation } from 'src/features/cseMachine/CseMachineAnimation';
 import { Layout } from 'src/features/cseMachine/CseMachineLayout';
+import { CseMachine as JavaCseMachine } from 'src/features/cseMachine/java/CseMachine';
 
 import { InterpreterOutput, OverallState } from '../../application/ApplicationTypes';
 import { HighlightedLines } from '../../editor/EditorTypes';
@@ -40,6 +42,7 @@ type State = {
   width: number;
   lastStep: boolean;
   stepLimitExceeded: boolean;
+  chapter: Chapter;
 };
 
 type CseMachineProps = OwnProps & StateProps & DispatchProps;
@@ -53,6 +56,7 @@ type StateProps = {
   changepointSteps: number[];
   needCseUpdate: boolean;
   machineOutput: InterpreterOutput[];
+  chapter: Chapter;
 };
 
 type OwnProps = {
@@ -85,25 +89,39 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
       width: this.calculateWidth(props.editorWidth),
       height: this.calculateHeight(props.sideContentHeight),
       lastStep: false,
-      stepLimitExceeded: false
+      stepLimitExceeded: false,
+      chapter: props.chapter
     };
-    CseMachine.init(
-      visualization => {
-        this.setState({ visualization }, () => CseAnimation.playAnimation());
-        if (visualization) this.props.handleAlertSideContent();
-      },
-      this.state.width,
-      this.state.height,
-      (segments: [number, number][]) => {
-        // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
-        // This comment is copied over from workspace saga
-        props.setEditorHighlightedLines(0, segments);
-      },
-      // We shouldn't be able to move slider to a step number beyond the step limit
-      isControlEmpty => {
-        this.setState({ stepLimitExceeded: false });
-      }
-    );
+    if (this.isJava()) {
+      JavaCseMachine.init(
+        visualization => this.setState({ visualization }),
+        (segments: [number, number][]) => {
+          props.setEditorHighlightedLines(0, segments);
+        }
+      );
+    } else {
+      CseMachine.init(
+        visualization => {
+          this.setState({ visualization }, () => CseAnimation.playAnimation());
+          if (visualization) this.props.handleAlertSideContent();
+        },
+        this.state.width,
+        this.state.height,
+        (segments: [number, number][]) => {
+          // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
+          // This comment is copied over from workspace saga
+          props.setEditorHighlightedLines(0, segments);
+        },
+        // We shouldn't be able to move slider to a step number beyond the step limit
+        isControlEmpty => {
+          this.setState({ stepLimitExceeded: false });
+        }
+      );
+    }
+  }
+
+  private isJava(): boolean {
+    return this.props.chapter === Chapter.FULL_JAVA;
   }
 
   private calculateWidth(editorWidth?: string) {
@@ -173,7 +191,11 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
     }
     if (prevProps.needCseUpdate && !this.props.needCseUpdate) {
       this.stepFirst();
-      CseMachine.clearCse();
+      if (this.isJava()) {
+        JavaCseMachine.clearCse();
+      } else {
+        CseMachine.clearCse();
+      }
     }
   }
 
@@ -210,45 +232,53 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
             onRelease={this.sliderRelease}
             value={this.state.value < 0 ? 0 : this.state.value}
           />
-          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
-            <ButtonGroup>
-              <Tooltip2 content="Control and Stash" compact>
-                <AnchorButton
-                  onMouseUp={() => {
-                    if (this.state.visualization) {
-                      CseMachine.toggleControlStash();
-                      CseMachine.redraw();
-                    }
-                  }}
-                  icon="layers"
-                  disabled={!this.state.visualization}
-                >
-                  <Checkbox
-                    checked={CseMachine.getControlStash()}
+          <div
+            style={{
+              display: 'flex',
+              justifyContent: this.isJava() ? 'center' : 'space-between',
+              alignItems: 'center'
+            }}
+          >
+            {!this.isJava() && (
+              <ButtonGroup>
+                <Tooltip2 content="Control and Stash" compact>
+                  <AnchorButton
+                    onMouseUp={() => {
+                      if (this.state.visualization) {
+                        CseMachine.toggleControlStash();
+                        CseMachine.redraw();
+                      }
+                    }}
+                    icon="layers"
                     disabled={!this.state.visualization}
-                    style={{ margin: 0 }}
-                  />
-                </AnchorButton>
-              </Tooltip2>
-              <Tooltip2 content="Truncate Control" compact>
-                <AnchorButton
-                  onMouseUp={() => {
-                    if (this.state.visualization) {
-                      CseMachine.toggleStackTruncated();
-                      CseMachine.redraw();
-                    }
-                  }}
-                  icon="minimize"
-                  disabled={!this.state.visualization}
-                >
-                  <Checkbox
-                    checked={CseMachine.getStackTruncated()}
+                  >
+                    <Checkbox
+                      checked={CseMachine.getControlStash()}
+                      disabled={!this.state.visualization}
+                      style={{ margin: 0 }}
+                    />
+                  </AnchorButton>
+                </Tooltip2>
+                <Tooltip2 content="Truncate Control" compact>
+                  <AnchorButton
+                    onMouseUp={() => {
+                      if (this.state.visualization) {
+                        CseMachine.toggleStackTruncated();
+                        CseMachine.redraw();
+                      }
+                    }}
+                    icon="minimize"
                     disabled={!this.state.visualization}
-                    style={{ margin: 0 }}
-                  />
-                </AnchorButton>
-              </Tooltip2>
-            </ButtonGroup>
+                  >
+                    <Checkbox
+                      checked={CseMachine.getStackTruncated()}
+                      disabled={!this.state.visualization}
+                      style={{ margin: 0 }}
+                    />
+                  </AnchorButton>
+                </Tooltip2>
+              </ButtonGroup>
+            )}
             <ButtonGroup>
               <Button
                 disabled={!this.state.visualization}
@@ -259,13 +289,19 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
                 disabled={!this.state.visualization}
                 icon="chevron-left"
                 onClick={
-                  CseMachine.getControlStash() ? this.stepPrevious : this.stepPrevChangepoint
+                  this.isJava() || CseMachine.getControlStash()
+                    ? this.stepPrevious
+                    : this.stepPrevChangepoint
                 }
               />
               <Button
                 disabled={!this.state.visualization}
                 icon="chevron-right"
-                onClick={CseMachine.getControlStash() ? this.stepNext : this.stepNextChangepoint}
+                onClick={
+                  this.isJava() || CseMachine.getControlStash()
+                    ? this.stepNext
+                    : this.stepNextChangepoint
+                }
               />
               <Button
                 disabled={!this.state.visualization}
@@ -273,33 +309,35 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
                 onClick={this.stepNextBreakpoint}
               />
             </ButtonGroup>
-            <ButtonGroup>
-              <Tooltip2 content="Print" compact>
-                <AnchorButton
-                  onMouseUp={() => {
-                    if (this.state.visualization) {
-                      CseMachine.togglePrintableMode();
-                      CseMachine.redraw();
-                    }
-                  }}
-                  icon="print"
-                  disabled={!this.state.visualization}
-                >
-                  <Checkbox
+            {!this.isJava() && (
+              <ButtonGroup>
+                <Tooltip2 content="Print" compact>
+                  <AnchorButton
+                    onMouseUp={() => {
+                      if (this.state.visualization) {
+                        CseMachine.togglePrintableMode();
+                        CseMachine.redraw();
+                      }
+                    }}
+                    icon="print"
                     disabled={!this.state.visualization}
-                    checked={CseMachine.getPrintableMode()}
-                    style={{ margin: 0 }}
+                  >
+                    <Checkbox
+                      disabled={!this.state.visualization}
+                      checked={CseMachine.getPrintableMode()}
+                      style={{ margin: 0 }}
+                    />
+                  </AnchorButton>
+                </Tooltip2>
+                <Tooltip2 content="Save" compact>
+                  <AnchorButton
+                    icon="floppy-disk"
+                    disabled={!this.state.visualization}
+                    onClick={Layout.exportImage}
                   />
-                </AnchorButton>
-              </Tooltip2>
-              <Tooltip2 content="Save" compact>
-                <AnchorButton
-                  icon="floppy-disk"
-                  disabled={!this.state.visualization}
-                  onClick={Layout.exportImage}
-                />
-              </Tooltip2>
-            </ButtonGroup>
+                </Tooltip2>
+              </ButtonGroup>
+            )}
           </div>
         </div>{' '}
         {this.state.visualization &&
@@ -331,14 +369,34 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
             className={Classes.RUNNING_TEXT}
             data-testid="cse-machine-default-text"
           >
-            The CSE machine generates control, stash and environment model diagrams following a
-            notation introduced in{' '}
-            <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank">
-              <i>
-                Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3,
-                Section 2
-              </i>
-            </a>
+            {this.isJava() ? (
+              <span>
+                The CSEC machine generates control, stash, environment and class model diagrams
+                adapted from the notation introduced in{' '}
+                <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank">
+                  <i>
+                    Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter
+                    3, Section 2
+                  </i>
+                </a>
+                {'. '}
+                You have chosen the sublanguage{' '}
+                <a href={`${Links.sourceDocs}java_csec/`} rel="noopener noreferrer" target="_blank">
+                  <i>Java CSEC</i>
+                </a>
+              </span>
+            ) : (
+              <span>
+                The CSE machine generates control, stash and environment model diagrams following a
+                notation introduced in{' '}
+                <a href={Links.textbookChapter3_2} rel="noopener noreferrer" target="_blank">
+                  <i>
+                    Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter
+                    3, Section 2
+                  </i>
+                </a>
+              </span>
+            )}
             .
             <br />
             <br /> On this tab, the REPL will be hidden from view, so do check that your code has no
@@ -371,13 +429,13 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
           <Button
             icon="plus"
             disabled={!this.state.visualization}
-            onClick={() => Layout.zoomStage(true, 5)}
+            onClick={() => this.zoomStage(true, 5)}
             style={{ marginBottom: '5px', borderRadius: '3px' }}
           />
           <Button
             icon="minus"
             disabled={!this.state.visualization}
-            onClick={() => Layout.zoomStage(false, 5)}
+            onClick={() => this.zoomStage(false, 5)}
             style={{ borderRadius: '3px' }}
           />
         </ButtonGroup>
@@ -385,6 +443,14 @@ class SideContentCseMachineBase extends React.Component<CseMachineProps, State>
     );
   }
 
+  private zoomStage = (isZoomIn: boolean, multiplier: number) => {
+    if (this.isJava()) {
+      JavaCseMachine.zoomStage(isZoomIn, multiplier);
+    } else {
+      Layout.zoomStage(isZoomIn, multiplier);
+    }
+  };
+
   private sliderRelease = (newValue: number) => {
     if (newValue === this.props.stepsTotal) {
       this.setState({ lastStep: true });
@@ -509,7 +575,8 @@ const mapStateToProps: MapStateToProps<StateProps, OwnProps, OverallState> = (
     breakpointSteps: workspace.breakpointSteps,
     changepointSteps: workspace.changepointSteps,
     needCseUpdate: workspace.updateCse,
-    machineOutput: workspace.output
+    machineOutput: workspace.output,
+    chapter: workspace.context.chapter
   };
 };
 
diff --git a/src/commons/utils/JavaHelper.ts b/src/commons/utils/JavaHelper.ts
index 42eea82175..fab36ed3c3 100644
--- a/src/commons/utils/JavaHelper.ts
+++ b/src/commons/utils/JavaHelper.ts
@@ -1,14 +1,21 @@
-import { compileFromSource, typeCheck } from 'java-slang';
+import { compileFromSource, ECE, typeCheck } from 'java-slang';
 import { BinaryWriter } from 'java-slang/dist/compiler/binary-writer';
 import setupJVM, { parseBin } from 'java-slang/dist/jvm';
 import { createModuleProxy, loadCachedFiles } from 'java-slang/dist/jvm/utils/integration';
 import { Context } from 'js-slang';
 import loadSourceModules from 'js-slang/dist/modules/loader';
+import { ErrorSeverity, ErrorType, Result, SourceError } from 'js-slang/dist/types';
 
+import { CseMachine } from '../../features/cseMachine/java/CseMachine';
 import Constants from './Constants';
 import DisplayBufferService from './DisplayBufferService';
 
-export async function javaRun(javaCode: string, context: Context) {
+export async function javaRun(
+  javaCode: string,
+  context: Context,
+  targetStep: number,
+  isUsingCse: boolean
+) {
   let compiled = {};
 
   const stderr = (type: 'TypeCheck' | 'Compile' | 'Runtime', msg: string) => {
@@ -99,6 +106,8 @@ export async function javaRun(javaCode: string, context: Context) {
     }
   };
 
+  if (isUsingCse) return await runJavaCseMachine(javaCode, targetStep, context);
+
   // load cached classfiles from IndexedDB
   return loadCachedFiles(() =>
     // Initial loader to fetch commonly used classfiles
@@ -148,3 +157,44 @@ export async function javaRun(javaCode: string, context: Context) {
       return { status: 'error' };
     });
 }
+
+export function visualizeJavaCseMachine({ context }: { context: ECE.Context }) {
+  try {
+    CseMachine.drawCse(context);
+  } catch (err) {
+    throw new Error('Java CSE machine is not enabled');
+  }
+}
+
+export async function runJavaCseMachine(code: string, targetStep: number, context: Context) {
+  const convertJavaErrorToJsError = (e: ECE.SourceError): SourceError => ({
+    type: ErrorType.RUNTIME,
+    severity: ErrorSeverity.ERROR,
+    // TODO update err source node location once location info is avail
+    location: {
+      start: {
+        line: 0,
+        column: 0
+      },
+      end: {
+        line: 0,
+        column: 0
+      }
+    },
+    explain: () => e.explain(),
+    elaborate: () => e.explain()
+  });
+  context.executionMethod = 'cse-machine';
+  return ECE.runECEvaluator(code, targetStep)
+    .then(result => {
+      context.runtime.envStepsTotal = result.context.totalSteps;
+      if (result.status === 'error') {
+        context.errors = result.context.errors.map(e => convertJavaErrorToJsError(e));
+      }
+      return result;
+    })
+    .catch(e => {
+      console.error(e);
+      return { status: 'error' } as Result;
+    });
+}
diff --git a/src/features/cseMachine/java/CseMachine.tsx b/src/features/cseMachine/java/CseMachine.tsx
new file mode 100644
index 0000000000..d259b4e021
--- /dev/null
+++ b/src/features/cseMachine/java/CseMachine.tsx
@@ -0,0 +1,152 @@
+import { ECE } from 'java-slang';
+import { KonvaEventObject } from 'konva/lib/Node';
+import React, { RefObject } from 'react';
+import { Layer, Rect, Stage } from 'react-konva';
+
+import { Config, ShapeDefaultProps } from './../CseMachineConfig';
+import { Control } from './components/Control';
+import { Environment } from './components/Environment';
+import { Stash } from './components/Stash';
+
+type SetVis = (vis: React.ReactNode) => void;
+type SetEditorHighlightedLines = (segments: [number, number][]) => void;
+
+export class CseMachine {
+  /** the unique key assigned to each node */
+  static key: number = 0;
+
+  /** callback function to update the visualization state in the SideContentCseMachine component */
+  private static setVis: SetVis;
+  /** function to highlight editor lines */
+  public static setEditorHighlightedLines: SetEditorHighlightedLines;
+
+  public static stageRef: RefObject<any> = React.createRef();
+  /** scale factor for zooming and out of canvas */
+  public static scaleFactor = 1.02;
+
+  static environment: Environment | undefined;
+  static control: Control | undefined;
+  static stash: Stash | undefined;
+
+  static init(setVis: SetVis, setEditorHighlightedLines: (segments: [number, number][]) => void) {
+    this.setVis = setVis;
+    this.setEditorHighlightedLines = setEditorHighlightedLines;
+  }
+
+  /** updates the visualization state in the SideContentCseMachine component based on
+   * the Java Slang context passed in */
+  static drawCse(context: ECE.Context) {
+    if (!this.setVis || !context.environment || !context.control || !context.stash) {
+      throw new Error('Java CSE Machine not initialized');
+    }
+
+    CseMachine.environment = new Environment(context.environment);
+    CseMachine.control = new Control(context.control);
+    CseMachine.stash = new Stash(context.stash);
+
+    this.setVis(this.draw());
+
+    // Set icon to blink.
+    const icon = document.getElementById('env_visualizer-icon');
+    icon && icon.classList.add('side-content-tab-alert');
+  }
+
+  static clearCse() {
+    if (this.setVis) {
+      this.setVis(undefined);
+      CseMachine.environment = undefined;
+      CseMachine.control = undefined;
+      CseMachine.stash = undefined;
+    }
+  }
+
+  /**
+   * Updates the scale of the stage after the user inititates a zoom in or out
+   * by scrolling or by the trackpad.
+   */
+  static zoomStage(event: KonvaEventObject<WheelEvent> | boolean, multiplier: number = 1) {
+    typeof event != 'boolean' && event.evt.preventDefault();
+    if (CseMachine.stageRef.current) {
+      const stage = CseMachine.stageRef.current;
+      const oldScale = stage.scaleX();
+      const { x: pointerX, y: pointerY } = stage.getPointerPosition();
+      const mousePointTo = {
+        x: (pointerX - stage.x()) / oldScale,
+        y: (pointerY - stage.y()) / oldScale
+      };
+
+      // zoom in or zoom out
+      const direction =
+        typeof event != 'boolean' ? (event.evt.deltaY > 0 ? -1 : 1) : event ? 1 : -1;
+
+      // Check if the zoom limits have been reached
+      if ((direction > 0 && oldScale < 3) || (direction < 0 && oldScale > 0.4)) {
+        const newScale =
+          direction > 0
+            ? oldScale * CseMachine.scaleFactor ** multiplier
+            : oldScale / CseMachine.scaleFactor ** multiplier;
+        stage.scale({ x: newScale, y: newScale });
+        if (typeof event !== 'boolean') {
+          const newPos = {
+            x: pointerX - mousePointTo.x * newScale,
+            y: pointerY - mousePointTo.y * newScale
+          };
+          stage.position(newPos);
+          stage.batchDraw();
+        }
+      }
+    }
+  }
+
+  static draw(): React.ReactNode {
+    const layout = (
+      <div className={'sa-cse-machine'} data-testid="sa-cse-machine">
+        <div
+          id="scroll-container"
+          style={{
+            width: window.innerWidth - 50,
+            height: window.innerHeight - 150,
+            overflow: 'hidden'
+          }}
+        >
+          <div
+            id="large-container"
+            style={{
+              width: Config.CanvasMinWidth,
+              height: Config.CanvasMinHeight,
+              overflow: 'hidden',
+              backgroundColor: Config.SA_BLUE
+            }}
+          >
+            <Stage
+              width={+Config.CanvasMinWidth}
+              height={+Config.CanvasMinHeight}
+              ref={this.stageRef}
+              draggable
+              onWheel={CseMachine.zoomStage}
+              className="draggable"
+            >
+              <Layer>
+                <Rect
+                  {...ShapeDefaultProps}
+                  x={0}
+                  y={0}
+                  width={Config.CanvasMinWidth}
+                  height={Config.CanvasMinHeight}
+                  fill={Config.SA_BLUE}
+                  key={CseMachine.key++}
+                  listening={false}
+                />
+                {this.control?.draw()}
+                {this.stash?.draw()}
+                {this.environment?.draw()}
+              </Layer>
+            </Stage>
+          </div>
+        </div>
+      </div>
+    );
+
+    return layout;
+  }
+}
diff --git a/src/features/cseMachine/java/components/Arrow.tsx b/src/features/cseMachine/java/components/Arrow.tsx
new file mode 100644
index 0000000000..ddbb8dfba7
--- /dev/null
+++ b/src/features/cseMachine/java/components/Arrow.tsx
@@ -0,0 +1,67 @@
+import { KonvaEventObject } from 'konva/lib/Node';
+import { Arrow as KonvaArrow, Group as KonvaGroup, Path as KonvaPath } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { IHoverable } from '../../CseMachineTypes';
+import {
+  setHoveredCursor,
+  setHoveredStyle,
+  setUnhoveredCursor,
+  setUnhoveredStyle
+} from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+
+/** this class encapsulates an Arrow to be drawn between 2 points */
+export class Arrow extends Visible implements IHoverable {
+  private static readonly TO_X_INDEX = 2;
+  private readonly _points: number[] = [];
+
+  constructor(fromX: number, fromY: number, toX: number, toY: number) {
+    super();
+    this._points.push(fromX, fromY, toX, toY);
+  }
+
+  setToX(x: number) {
+    this._points[Arrow.TO_X_INDEX] = x;
+  }
+
+  onMouseEnter(e: KonvaEventObject<MouseEvent>) {
+    setHoveredStyle(e.currentTarget);
+    setHoveredCursor(e.currentTarget);
+  }
+
+  onMouseLeave(e: KonvaEventObject<MouseEvent>) {
+    setUnhoveredStyle(e.currentTarget);
+    setUnhoveredCursor(e.currentTarget);
+  }
+
+  draw() {
+    const path = `M ${this._points[0]} ${this._points[1]} L ${this._points[2]} ${this._points[3]}`;
+    return (
+      <KonvaGroup
+        key={CseMachine.key++}
+        onMouseEnter={e => this.onMouseEnter(e)}
+        onMouseLeave={e => this.onMouseLeave(e)}
+      >
+        <KonvaPath
+          {...ShapeDefaultProps}
+          stroke={String(Config.SA_WHITE)}
+          strokeWidth={Number(Config.ArrowStrokeWidth)}
+          hitStrokeWidth={Number(Config.ArrowHitStrokeWidth)}
+          data={path}
+          key={CseMachine.key++}
+        />
+        <KonvaArrow
+          {...ShapeDefaultProps}
+          points={this._points.slice(this._points.length - 4)}
+          fill={String(Config.SA_WHITE)}
+          strokeEnabled={false}
+          pointerWidth={Number(Config.ArrowHeadSize)}
+          pointerLength={Number(Config.ArrowHeadSize)}
+          key={CseMachine.key++}
+        />
+      </KonvaGroup>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Binding.tsx b/src/features/cseMachine/java/components/Binding.tsx
new file mode 100644
index 0000000000..6ecdb6cb47
--- /dev/null
+++ b/src/features/cseMachine/java/components/Binding.tsx
@@ -0,0 +1,96 @@
+import { ECE } from 'java-slang';
+import React from 'react';
+
+import { Visible } from '../../components/Visible';
+import { Config } from '../../CseMachineConfig';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Method } from './Method';
+import { Text } from './Text';
+import { Variable } from './Variable';
+
+/** a Binding is a key-value pair in a Frame */
+export class Binding extends Visible {
+  private readonly _name: Text;
+
+  private readonly _value: Variable | Method | Text;
+  // Only Method has arrow.
+  private readonly _arrow: Arrow | undefined;
+
+  constructor(name: ECE.Name, value: ECE.Value, x: number, y: number) {
+    super();
+
+    // Position.
+    this._x = x;
+    this._y = y;
+
+    if (value.kind === ECE.StructType.CLOSURE) {
+      // Name.
+      this._name = new Text(
+        name + Config.VariableColon, // := is part of name
+        this.x(),
+        this.y()
+      );
+      // Value.
+      this._value = new Method(
+        this._name.x() + this._name.width(),
+        this.y() + this._name.height() / 2,
+        value
+      );
+      this._arrow = new Arrow(
+        this._name.x() + this._name.width(),
+        this._name.y() + this._name.height() / 2,
+        this._value.x(),
+        this._value.y()
+      );
+    } else if (value.kind === ECE.StructType.VARIABLE) {
+      // Name.
+      this._name = new Text(
+        name + Config.VariableColon, // := is part of name
+        this.x(),
+        this.y() + Config.FontSize + Config.TextPaddingX
+      );
+      // Value.
+      this._value = new Variable(this._name.x() + this._name.width(), this.y(), value);
+    } /*if (value.kind === StructType.CLASS)*/ else {
+      // Dummy value as class will nvr be drawn.
+      // Name.
+      this._name = new Text(
+        name + Config.VariableColon, // := is part of name
+        this.x(),
+        this.y() + Config.FontSize + Config.TextPaddingX
+      );
+      // Value.
+      this._value = new Text(
+        '',
+        this._name.x() + this._name.width() + Config.TextPaddingX,
+        this.y() + Config.TextPaddingX
+      );
+    }
+
+    // Height and width.
+    this._height = Math.max(this._name.height(), this._value.height());
+    this._width = this._value.x() + this._value.width() - this._name.x();
+  }
+
+  get value() {
+    return this._value;
+  }
+
+  setArrowToX(x: number) {
+    this._arrow?.setToX(x);
+  }
+
+  draw(): React.ReactNode {
+    return (
+      <React.Fragment key={CseMachine.key++}>
+        {/* Name */}
+        {this._name.draw()}
+
+        {/* Value */}
+        {this._value.draw()}
+        {this._arrow?.draw()}
+      </React.Fragment>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Control.tsx b/src/features/cseMachine/java/components/Control.tsx
new file mode 100644
index 0000000000..8305ec1551
--- /dev/null
+++ b/src/features/cseMachine/java/components/Control.tsx
@@ -0,0 +1,197 @@
+import { astToString, ECE } from 'java-slang';
+import { Group } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { CseMachine } from '../CseMachine';
+import { ControlItem } from './ControlItem';
+
+export class Control extends Visible {
+  private readonly _controlItems: ControlItem[] = [];
+
+  constructor(control: ECE.Control) {
+    super();
+
+    // Position.
+    this._x = ControlStashConfig.ControlPosX;
+    this._y =
+      ControlStashConfig.ControlPosY +
+      ControlStashConfig.StashItemHeight +
+      ControlStashConfig.StashItemTextPadding * 2;
+
+    // Create each ControlItem.
+    let controlItemY: number = this._y;
+    control.getStack().forEach((controlItem, index) => {
+      const controlItemText = this.getControlItemString(controlItem);
+
+      const controlItemStroke =
+        index === control.getStack().length - 1
+          ? Config.SA_CURRENT_ITEM
+          : ControlStashConfig.SA_WHITE;
+
+      // TODO reference draw ltr?
+      const controlItemReference =
+        ECE.isInstr(controlItem) && controlItem.instrType === ECE.InstrType.ENV
+          ? CseMachine.environment?.frames.find(f => f.frame === (controlItem as ECE.EnvInstr).env)
+          : undefined;
+
+      const controlItemTooltip = this.getControlItemTooltip(controlItem);
+      this.getControlItemTooltip(controlItem);
+
+      const node = ECE.isNode(controlItem) ? controlItem : controlItem.srcNode;
+      const highlightOnHover = () => {
+        let start = -1;
+        let end = -1;
+        if (node.location) {
+          start = node.location.startLine - 1;
+          end = node.location.endLine ? node.location.endLine - 1 : start;
+        }
+        CseMachine.setEditorHighlightedLines([[start, end]]);
+      };
+      const unhighlightOnHover = () => CseMachine.setEditorHighlightedLines([]);
+
+      const currControlItem = new ControlItem(
+        controlItemY,
+        controlItemText,
+        controlItemStroke,
+        controlItemReference,
+        controlItemTooltip,
+        highlightOnHover,
+        unhighlightOnHover
+      );
+
+      this._controlItems.push(currControlItem);
+      controlItemY += currControlItem.height();
+    });
+
+    // Height and width.
+    this._height = controlItemY - this._y;
+    // TODO cal real width?
+    this._width = ControlStashConfig.ControlItemWidth;
+  }
+
+  draw(): React.ReactNode {
+    return (
+      <Group key={CseMachine.key++} ref={this.ref}>
+        {this._controlItems.map(c => c.draw())}
+      </Group>
+    );
+  }
+
+  private getControlItemString = (controlItem: ECE.ControlItem): string => {
+    if (ECE.isNode(controlItem)) {
+      return astToString(controlItem);
+    }
+
+    switch (controlItem.instrType) {
+      case ECE.InstrType.RESET:
+        return 'return';
+      case ECE.InstrType.ASSIGNMENT:
+        return 'asgn';
+      case ECE.InstrType.BINARY_OP:
+        const binOpInstr = controlItem as ECE.BinOpInstr;
+        return binOpInstr.symbol;
+      case ECE.InstrType.POP:
+        return 'pop';
+      case ECE.InstrType.INVOCATION:
+        const appInstr = controlItem as ECE.InvInstr;
+        return `invoke ${appInstr.arity}`;
+      case ECE.InstrType.ENV:
+        return 'env';
+      case ECE.InstrType.MARKER:
+        return 'mark';
+      case ECE.InstrType.EVAL_VAR:
+        const evalVarInstr = controlItem as ECE.EvalVarInstr;
+        return `name ${evalVarInstr.symbol}`;
+      case ECE.InstrType.NEW:
+        const newInstr = controlItem as ECE.NewInstr;
+        return `new ${newInstr.c.frame.name}`;
+      case ECE.InstrType.RES_TYPE:
+        const resTypeInstr = controlItem as ECE.ResTypeInstr;
+        return `res_type ${
+          resTypeInstr.value.kind === 'Class'
+            ? resTypeInstr.value.frame.name
+            : astToString(resTypeInstr.value)
+        }`;
+      case ECE.InstrType.RES_TYPE_CONT:
+        const resTypeContInstr = controlItem as ECE.ResTypeContInstr;
+        return `res_type_cont ${resTypeContInstr.name}`;
+      case ECE.InstrType.RES_OVERLOAD:
+        const resOverloadInstr = controlItem as ECE.ResOverloadInstr;
+        return `res_overload ${resOverloadInstr.name} ${resOverloadInstr.arity}`;
+      case ECE.InstrType.RES_OVERRIDE:
+        return `res_override`;
+      case ECE.InstrType.RES_CON_OVERLOAD:
+        const resConOverloadInstr = controlItem as ECE.ResConOverloadInstr;
+        return `res_con_overload ${resConOverloadInstr.arity}`;
+      case ECE.InstrType.RES:
+        const resInstr = controlItem as ECE.ResInstr;
+        return `res ${resInstr.name}`;
+      case ECE.InstrType.DEREF:
+        return 'deref';
+      default:
+        return 'INSTRUCTION';
+    }
+  };
+
+  private getControlItemTooltip = (controlItem: ECE.ControlItem): string => {
+    if (ECE.isNode(controlItem)) {
+      return astToString(controlItem);
+    }
+
+    switch (controlItem.instrType) {
+      case ECE.InstrType.RESET:
+        return 'Skip control items until marker instruction is reached';
+      case ECE.InstrType.ASSIGNMENT:
+        return 'Assign value on top of stash to location on top of stash';
+      case ECE.InstrType.BINARY_OP:
+        const binOpInstr = controlItem as ECE.BinOpInstr;
+        return `Perform ${binOpInstr.symbol} on top 2 stash values`;
+      case ECE.InstrType.POP:
+        return 'Pop most recently pushed value from stash';
+      case ECE.InstrType.INVOCATION:
+        const appInstr = controlItem as ECE.InvInstr;
+        return `Invoke method with ${appInstr.arity} argument${appInstr.arity === 1 ? '' : 's'}`;
+      case ECE.InstrType.ENV:
+        return 'Set current environment to this environment';
+      case ECE.InstrType.MARKER:
+        return 'Mark return address';
+      case ECE.InstrType.EVAL_VAR:
+        const evalVarInstr = controlItem as ECE.EvalVarInstr;
+        return `name ${evalVarInstr.symbol}`;
+      case ECE.InstrType.NEW:
+        const newInstr = controlItem as ECE.NewInstr;
+        return `Create new instance of class ${newInstr.c.frame.name}`;
+      case ECE.InstrType.RES_TYPE:
+        const resTypeInstr = controlItem as ECE.ResTypeInstr;
+        return `Resolve type of ${
+          resTypeInstr.value.kind === 'Class'
+            ? resTypeInstr.value.frame.name
+            : astToString(resTypeInstr.value)
+        }`;
+      case ECE.InstrType.RES_TYPE_CONT:
+        const resTypeContInstr = controlItem as ECE.ResTypeContInstr;
+        return `Resolve type of ${resTypeContInstr.name} in most recently pushed type from stash`;
+      case ECE.InstrType.RES_OVERLOAD:
+        const resOverloadInstr = controlItem as ECE.ResOverloadInstr;
+        return `Resolve overloading of method ${resOverloadInstr.name} with ${
+          resOverloadInstr.arity
+        } argument${resOverloadInstr.arity === 1 ? '' : 's'}`;
+      case ECE.InstrType.RES_OVERRIDE:
+        return 'Resolve overriding of resolved method on top of stash';
+      case ECE.InstrType.RES_CON_OVERLOAD:
+        const resConOverloadInstr = controlItem as ECE.ResConOverloadInstr;
+        return `Resolve constructor overloading of class on stash with ${
+          resConOverloadInstr.arity
+        } argument${resConOverloadInstr.arity === 1 ? '' : 's'}`;
+      case ECE.InstrType.RES:
+        const resInstr = controlItem as ECE.ResInstr;
+        return `Resolve field ${resInstr.name} of most recently pushed value from stash`;
+      case ECE.InstrType.DEREF:
+        return 'Dereference most recently pushed value from stash';
+      default:
+        return 'INSTRUCTION';
+    }
+  };
+}
diff --git a/src/features/cseMachine/java/components/ControlItem.tsx b/src/features/cseMachine/java/components/ControlItem.tsx
new file mode 100644
index 0000000000..55fa748887
--- /dev/null
+++ b/src/features/cseMachine/java/components/ControlItem.tsx
@@ -0,0 +1,151 @@
+import { KonvaEventObject } from 'konva/lib/Node';
+import React, { RefObject } from 'react';
+import { Label, Tag, Text } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { IHoverable } from '../../CseMachineTypes';
+import {
+  getTextHeight,
+  setHoveredCursor,
+  setHoveredStyle,
+  setUnhoveredCursor,
+  setUnhoveredStyle,
+  truncateText
+} from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Frame } from './Frame';
+
+export class ControlItem extends Visible implements IHoverable {
+  private readonly _arrow: Arrow | undefined;
+  private readonly _tooltipRef: RefObject<any>;
+
+  constructor(
+    y: number,
+
+    private readonly _text: string,
+    private readonly _stroke: string,
+
+    reference: Frame | undefined,
+
+    private readonly _tooltip: string,
+    private readonly highlightOnHover: () => void,
+    private readonly unhighlightOnHover: () => void
+  ) {
+    super();
+
+    // Position.
+    this._x = ControlStashConfig.ControlPosX;
+    this._y = y;
+
+    // Text.
+    this._text = truncateText(
+      this._text,
+      ControlStashConfig.ControlMaxTextWidth,
+      ControlStashConfig.ControlMaxTextHeight
+    );
+
+    // Tooltip.
+    this._tooltipRef = React.createRef();
+
+    // Height and width.
+    this._height =
+      getTextHeight(this._text, ControlStashConfig.ControlMaxTextWidth) +
+      ControlStashConfig.ControlItemTextPadding * 2;
+    this._width = ControlStashConfig.ControlItemWidth;
+
+    // Arrow
+    if (reference) {
+      this._arrow = new Arrow(
+        this._x + this._width,
+        this._y + this._height / 2,
+        reference.x(),
+        reference.y() + reference.height() / 2 + reference.name.height()
+      );
+    }
+  }
+
+  private isCurrentItem = (): boolean => {
+    return this._stroke === Config.SA_CURRENT_ITEM;
+  };
+
+  onMouseEnter = (e: KonvaEventObject<MouseEvent>): void => {
+    this.highlightOnHover();
+    !this.isCurrentItem() && setHoveredStyle(e.currentTarget);
+    setHoveredCursor(e.currentTarget);
+    this._tooltipRef.current.show();
+  };
+
+  onMouseLeave = (e: KonvaEventObject<MouseEvent>): void => {
+    this.unhighlightOnHover();
+    !this.isCurrentItem() && setUnhoveredStyle(e.currentTarget);
+    setUnhoveredCursor(e.currentTarget);
+    this._tooltipRef.current.hide();
+  };
+
+  draw(): React.ReactNode {
+    const textProps = {
+      fill: ControlStashConfig.SA_WHITE,
+      padding: ControlStashConfig.ControlItemTextPadding,
+      fontFamily: ControlStashConfig.FontFamily,
+      fontSize: ControlStashConfig.FontSize,
+      fontStyle: ControlStashConfig.FontStyle,
+      fontVariant: ControlStashConfig.FontVariant
+    };
+    const tagProps = {
+      stroke: this._stroke,
+      cornerRadius: ControlStashConfig.ControlItemCornerRadius
+    };
+    return (
+      <React.Fragment key={CseMachine.key++}>
+        {/* Text */}
+        <Label
+          x={this.x()}
+          y={this.y()}
+          onMouseEnter={this.onMouseEnter}
+          onMouseLeave={this.onMouseLeave}
+          key={CseMachine.key++}
+        >
+          <Tag {...ShapeDefaultProps} {...tagProps} key={CseMachine.key++} />
+          <Text
+            {...ShapeDefaultProps}
+            {...textProps}
+            text={this._text}
+            width={this.width()}
+            height={this.height()}
+            key={CseMachine.key++}
+          />
+        </Label>
+
+        {/* Tooltip */}
+        <Label
+          x={this.x() + this.width() + ControlStashConfig.TooltipMargin}
+          y={this.y() + ControlStashConfig.TooltipMargin}
+          visible={false}
+          ref={this._tooltipRef}
+          key={CseMachine.key++}
+        >
+          <Tag
+            {...ShapeDefaultProps}
+            stroke="black"
+            fill={'black'}
+            opacity={ControlStashConfig.TooltipOpacity}
+            key={CseMachine.key++}
+          />
+          <Text
+            {...ShapeDefaultProps}
+            {...textProps}
+            text={this._tooltip}
+            padding={ControlStashConfig.TooltipPadding}
+            key={CseMachine.key++}
+          />
+        </Label>
+
+        {/* Arrow */}
+        {this._arrow?.draw()}
+      </React.Fragment>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Environment.tsx b/src/features/cseMachine/java/components/Environment.tsx
new file mode 100644
index 0000000000..f8be475a31
--- /dev/null
+++ b/src/features/cseMachine/java/components/Environment.tsx
@@ -0,0 +1,217 @@
+import { ECE } from 'java-slang';
+import { Group } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Frame } from './Frame';
+import { Line } from './Line';
+import { Obj } from './Object';
+import { Variable } from './Variable';
+
+export class Environment extends Visible {
+  private readonly _methodFrames: Frame[] = [];
+  private readonly _objects: Obj[] = [];
+  private readonly _classFrames: Frame[] = [];
+  private readonly _lines: Line[] = [];
+
+  constructor(environment: ECE.Environment) {
+    super();
+
+    // Position.
+    this._x =
+      ControlStashConfig.ControlPosX +
+      ControlStashConfig.ControlItemWidth +
+      2 * Config.CanvasPaddingX;
+    this._y =
+      ControlStashConfig.StashPosY + ControlStashConfig.StashItemHeight + 2 * Config.CanvasPaddingY;
+
+    // Create method frames.
+    const methodFramesX = this._x;
+    let methodFramesY: number = this._y;
+    let methodFramesWidth = Number(Config.FrameMinWidth);
+    environment.global.children.forEach(env => {
+      if (env.name.includes('(')) {
+        let currEnv: ECE.EnvNode | undefined = env;
+        let parentFrame;
+        while (currEnv) {
+          const stroke = currEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE;
+          const frame = new Frame(currEnv, methodFramesX, methodFramesY, stroke);
+          this._methodFrames.push(frame);
+          methodFramesY += frame.height() + Config.FramePaddingY;
+          methodFramesWidth = Math.max(methodFramesWidth, frame.width());
+          parentFrame && frame.setParent(parentFrame);
+
+          parentFrame = frame;
+          currEnv = currEnv.children.length ? currEnv.children[0] : undefined;
+        }
+      }
+    });
+
+    // Create objects.
+    const objectFramesX = methodFramesX + methodFramesWidth + Config.FrameMinWidth;
+    let objectFramesY: number = this._y;
+    let objectFramesWidth = Number(Config.FrameMinWidth);
+    environment.objects.forEach(obj => {
+      const objectFrames: Frame[] = [];
+      let objectFrameWidth = Number(Config.FrameMinWidth);
+
+      // Get top env.
+      let env: ECE.EnvNode | undefined = obj.frame;
+      while (env.parent) {
+        env = env.parent;
+      }
+
+      // Create frame top-down.
+      while (env) {
+        const stroke = env === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE;
+        const frame = new Frame(env, objectFramesX, objectFramesY, stroke);
+        // No padding btwn obj frames thus no arrows required.
+        objectFramesY += frame.height();
+        objectFramesWidth = Math.max(objectFramesWidth, frame.width());
+
+        env = env.children.length ? env.children[0] : undefined;
+
+        objectFrames.push(frame);
+        objectFrameWidth = Math.max(objectFrameWidth, frame.width());
+      }
+
+      // Standardize obj frames width.
+      objectFrames.forEach(o => o.setWidth(objectFrameWidth));
+
+      // Only add padding btwn objects.
+      objectFramesY += Config.FramePaddingY;
+
+      this._objects.push(new Obj(objectFrames, obj));
+    });
+
+    // Create class frames.
+    const classFramesX = objectFramesX + objectFramesWidth + Config.FrameMinWidth;
+    let classFramesY = this._y;
+    for (const c of environment.global.frame.values()) {
+      const classEnv = (c as ECE.Class).frame;
+      const classFrameStroke =
+        classEnv === environment.current ? Config.SA_CURRENT_ITEM : Config.SA_WHITE;
+      const highlightOnHover = () => {
+        const node = (c as ECE.Class).classDecl;
+        let start = -1;
+        let end = -1;
+        if (node.location) {
+          start = node.location.startLine - 1;
+          end = node.location.endLine ? node.location.endLine - 1 : start;
+        }
+        CseMachine.setEditorHighlightedLines([[start, end]]);
+      };
+      const unhighlightOnHover = () => CseMachine.setEditorHighlightedLines([]);
+      const classFrame = new Frame(
+        classEnv,
+        classFramesX,
+        classFramesY,
+        classFrameStroke,
+        '',
+        highlightOnHover,
+        unhighlightOnHover
+      );
+      const superClassName = (c as ECE.Class).superclass?.frame.name;
+      if (superClassName) {
+        const parentFrame = this._classFrames.find(f => f.name.text === superClassName)!;
+        classFrame.setParent(parentFrame);
+      }
+      this._classFrames.push(classFrame);
+      classFramesY += classFrame.height() + Config.FramePaddingY;
+    }
+
+    // Draw arrow for var ref in mtd frames to corresponding obj.
+    this._methodFrames.forEach(mf => {
+      mf.bindings.forEach(b => {
+        if (b.value instanceof Variable && b.value.variable.value.kind === ECE.StructType.OBJECT) {
+          const objFrame = b.value.variable.value.frame;
+          const matchingObj = this._objects.filter(o => o.getFrame().frame === objFrame)[0];
+          b.value.value = new Arrow(
+            b.value.x() + b.value.width() / 2,
+            b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2,
+            matchingObj.x(),
+            matchingObj.y() + matchingObj.height() / 2
+          );
+        }
+      });
+    });
+
+    // Draw arrow for var ref in obj frames to corresponding var or obj.
+    this._objects
+      .flatMap(obj => obj.frames)
+      .forEach(of => {
+        of.bindings.forEach(b => {
+          if (
+            b.value instanceof Variable &&
+            b.value.variable.value.kind === ECE.StructType.VARIABLE
+          ) {
+            const variable = b.value.variable.value;
+            const matchingVariable = this._classFrames
+              .flatMap(c => c.bindings)
+              .filter(b => b.value instanceof Variable && b.value.variable === variable)[0]
+              .value as Variable;
+            b.value.value = new Arrow(
+              b.value.x() + b.value.width() / 2,
+              b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2,
+              matchingVariable.x(),
+              matchingVariable.y() + matchingVariable.type.height()
+            );
+          }
+          if (
+            b.value instanceof Variable &&
+            b.value.variable.value.kind === ECE.StructType.OBJECT
+          ) {
+            const obj = b.value.variable.value.frame;
+            const matchingObj = this._objects.find(o => o.getFrame().frame === obj)!;
+            // Variable always has a box.
+            b.value.value = new Arrow(
+              b.value.x() + b.value.width() / 2,
+              b.value.y() + b.value.type.height() + (b.value.height() - b.value.type.height()) / 2,
+              matchingObj.x(),
+              matchingObj.y() + matchingObj.height() / 2
+            );
+          }
+        });
+      });
+
+    // Draw line for obj to class.
+    this._objects.forEach(obj => {
+      const matchingClass = this._classFrames.find(
+        c => c.name.text === obj.object.class.frame.name
+      )!;
+      const line = new Line(
+        obj.x() + obj.width(),
+        obj.y() + obj.height() / 2,
+        matchingClass.x(),
+        matchingClass.y() + matchingClass.height() / 2 + matchingClass.name.height()
+      );
+      this._lines.push(line);
+    });
+  }
+
+  get classes() {
+    return this._classFrames;
+  }
+
+  get objects() {
+    return this._objects.flatMap(obj => obj.frames);
+  }
+
+  get frames() {
+    return this._methodFrames;
+  }
+
+  draw(): React.ReactNode {
+    return (
+      <Group key={CseMachine.key++}>
+        {this._methodFrames.map(f => f.draw())}
+        {this._objects.flatMap(obj => obj.frames).map(f => f.draw())}
+        {this._classFrames.map(f => f.draw())}
+        {this._lines.map(f => f.draw())}
+      </Group>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Frame.tsx b/src/features/cseMachine/java/components/Frame.tsx
new file mode 100644
index 0000000000..2c7ec35570
--- /dev/null
+++ b/src/features/cseMachine/java/components/Frame.tsx
@@ -0,0 +1,153 @@
+import { ECE } from 'java-slang';
+import { KonvaEventObject } from 'konva/lib/Node';
+import React, { RefObject } from 'react';
+import { Group, Label, Rect, Tag, Text as KonvaText } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { IHoverable } from '../../CseMachineTypes';
+import { setHoveredCursor, setUnhoveredCursor } from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Binding } from './Binding';
+import { Method } from './Method';
+import { Text } from './Text';
+
+export class Frame extends Visible implements IHoverable {
+  readonly tooltipRef: RefObject<any>;
+
+  readonly bindings: Binding[] = [];
+  readonly name: Text;
+  private parent: Frame | undefined;
+
+  constructor(
+    readonly frame: ECE.EnvNode,
+    x: number,
+    y: number,
+    readonly stroke: string,
+
+    readonly tooltip?: string,
+    readonly highlightOnHover?: () => void,
+    readonly unhighlightOnHover?: () => void
+  ) {
+    super();
+
+    this._x = x;
+    this._y = y;
+
+    this.name = new Text(frame.name, this._x + Config.FramePaddingX, this._y);
+
+    this._width = Math.max(Config.FrameMinWidth, this.name.width() + 2 * Config.FramePaddingX);
+    this._height = Config.FramePaddingY + this.name.height();
+
+    // Create binding for each key-value pair
+    let bindingY: number = this._y + this.name.height() + Config.FramePaddingY;
+    for (const [key, data] of frame.frame) {
+      const currBinding: Binding = new Binding(key, data, this._x + Config.FramePaddingX, bindingY);
+      this.bindings.push(currBinding);
+      bindingY += currBinding.height() + Config.FramePaddingY;
+      this._width = Math.max(this._width, currBinding.width() + 2 * Config.FramePaddingX);
+      this._height += currBinding.height() + Config.FramePaddingY;
+    }
+
+    // Set x of Method aft knowing frame width.
+    this.bindings
+      .filter(b => b.value instanceof Method)
+      .forEach(b => {
+        (b.value as Method).setX(this._x + this._width + Config.FramePaddingX);
+        b.setArrowToX(this._x + this._width + Config.FramePaddingX);
+      });
+
+    this.tooltipRef = React.createRef();
+  }
+
+  setWidth(width: number) {
+    this._width = width;
+  }
+
+  setParent(parent: Frame) {
+    this.parent = parent;
+  }
+
+  onMouseEnter = (e: KonvaEventObject<MouseEvent>) => {
+    this.highlightOnHover && this.highlightOnHover();
+    (this.tooltip || this.highlightOnHover) && setHoveredCursor(e.currentTarget);
+    this.tooltip && this.tooltipRef.current.show();
+  };
+
+  onMouseLeave = (e: KonvaEventObject<MouseEvent>) => {
+    this.unhighlightOnHover && this.unhighlightOnHover();
+    (this.tooltip || this.unhighlightOnHover) && setUnhoveredCursor(e.currentTarget);
+    this.tooltip && this.tooltipRef.current.hide();
+  };
+
+  draw(): React.ReactNode {
+    const textProps = {
+      fill: ControlStashConfig.SA_WHITE.toString(),
+      padding: Number(ControlStashConfig.ControlItemTextPadding),
+      fontFamily: ControlStashConfig.FontFamily.toString(),
+      fontSize: Number(ControlStashConfig.FontSize),
+      fontStyle: ControlStashConfig.FontStyle.toString(),
+      fontVariant: ControlStashConfig.FontVariant.toString()
+    };
+
+    return (
+      <Group key={CseMachine.key++}>
+        <Rect
+          {...ShapeDefaultProps}
+          x={this.x()}
+          y={this.y() + this.name.height()}
+          width={this.width()}
+          height={this.height()}
+          stroke={this.stroke}
+          cornerRadius={Number(Config.FrameCornerRadius)}
+          onMouseEnter={this.onMouseEnter}
+          onMouseLeave={this.onMouseLeave}
+          key={CseMachine.key++}
+        />
+        {/* Frame name */}
+        {this.name.draw()}
+
+        {/* Frame */}
+        {this.bindings.map(binding => binding.draw())}
+
+        {/* Frame parent */}
+        {this.parent &&
+          new Arrow(
+            this._x + Config.FramePaddingX / 2,
+            this._y + this.name.height(),
+            this.parent.x() + Config.FramePaddingX / 2,
+            // TODO WHY NEED TO ADD NAME HEIGHT?
+            this.parent.y() + this.parent.height() + this.name?.height()
+          ).draw()}
+
+        {/* Frame tooltip */}
+        {this.tooltip && (
+          <Label
+            x={this.x() + this.width() + ControlStashConfig.TooltipMargin}
+            y={this.y() + ControlStashConfig.TooltipMargin}
+            visible={false}
+            ref={this.tooltipRef}
+            key={CseMachine.key++}
+          >
+            <Tag
+              {...ShapeDefaultProps}
+              stroke="black"
+              fill={'black'}
+              opacity={Number(ControlStashConfig.TooltipOpacity)}
+              key={CseMachine.key++}
+            />
+            <KonvaText
+              {...ShapeDefaultProps}
+              {...textProps}
+              text={this.tooltip}
+              padding={Number(ControlStashConfig.TooltipPadding)}
+              key={CseMachine.key++}
+            />
+          </Label>
+        )}
+      </Group>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Line.tsx b/src/features/cseMachine/java/components/Line.tsx
new file mode 100644
index 0000000000..67323c32b5
--- /dev/null
+++ b/src/features/cseMachine/java/components/Line.tsx
@@ -0,0 +1,58 @@
+import { KonvaEventObject } from 'konva/lib/Node';
+import { Group, Path } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { IHoverable } from '../../CseMachineTypes';
+import {
+  setHoveredCursor,
+  setHoveredStyle,
+  setUnhoveredCursor,
+  setUnhoveredStyle
+} from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+
+/** this class encapsulates a Line to be drawn between 2 points */
+export class Line extends Visible implements IHoverable {
+  private static readonly TO_X_INDEX = 2;
+  private readonly _points: number[] = [];
+
+  constructor(fromX: number, fromY: number, toX: number, toY: number) {
+    super();
+    this._points.push(fromX, fromY, toX, toY);
+  }
+
+  setToX(x: number) {
+    this._points[Line.TO_X_INDEX] = x;
+  }
+
+  onMouseEnter(e: KonvaEventObject<MouseEvent>) {
+    setHoveredStyle(e.currentTarget);
+    setHoveredCursor(e.currentTarget);
+  }
+
+  onMouseLeave(e: KonvaEventObject<MouseEvent>) {
+    setUnhoveredStyle(e.currentTarget);
+    setUnhoveredCursor(e.currentTarget);
+  }
+
+  draw() {
+    const path = `M ${this._points[0]} ${this._points[1]} L ${this._points[2]} ${this._points[3]}`;
+    return (
+      <Group
+        key={CseMachine.key++}
+        onMouseEnter={e => this.onMouseEnter(e)}
+        onMouseLeave={e => this.onMouseLeave(e)}
+      >
+        <Path
+          {...ShapeDefaultProps}
+          stroke={String(Config.SA_WHITE)}
+          strokeWidth={Number(Config.ArrowStrokeWidth)}
+          hitStrokeWidth={Number(Config.ArrowHitStrokeWidth)}
+          data={path}
+          key={CseMachine.key++}
+        />
+      </Group>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Method.tsx b/src/features/cseMachine/java/components/Method.tsx
new file mode 100644
index 0000000000..45846e10e6
--- /dev/null
+++ b/src/features/cseMachine/java/components/Method.tsx
@@ -0,0 +1,118 @@
+import { astToString, ECE } from 'java-slang';
+import { KonvaEventObject } from 'konva/lib/Node';
+import React, { RefObject } from 'react';
+import { Circle, Group, Label, Tag, Text } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { IHoverable } from '../../CseMachineTypes';
+import {
+  getTextHeight,
+  getTextWidth,
+  setHoveredCursor,
+  setUnhoveredCursor
+} from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+
+export class Method extends Visible implements IHoverable {
+  private _centerX: number;
+
+  private readonly _tooltipRef: RefObject<any>;
+  private readonly _tooltip: string;
+
+  constructor(
+    x: number,
+    y: number,
+    private readonly _method: ECE.Closure
+  ) {
+    super();
+
+    // Position.
+    this._x = x;
+    this._y = y;
+
+    // Circle.
+    this._centerX = this._x + Config.FnRadius * 2;
+
+    // Tooltip.
+    this._tooltipRef = React.createRef();
+    this._tooltip = astToString(this._method.mtdOrCon);
+  }
+
+  get method() {
+    return this._method;
+  }
+
+  setX(x: number) {
+    this._x = x;
+    this._centerX = this._x + Config.FnRadius * 2;
+  }
+
+  onMouseEnter = (e: KonvaEventObject<MouseEvent>) => {
+    setHoveredCursor(e.currentTarget);
+    this._tooltipRef.current.show();
+  };
+
+  onMouseLeave = (e: KonvaEventObject<MouseEvent>) => {
+    setUnhoveredCursor(e.currentTarget);
+    this._tooltipRef.current.hide();
+  };
+
+  draw(): React.ReactNode {
+    return (
+      <React.Fragment key={CseMachine.key++}>
+        <Group
+          onMouseEnter={e => this.onMouseEnter(e)}
+          onMouseLeave={e => this.onMouseLeave(e)}
+          // ref={this.ref}
+          key={CseMachine.key++}
+        >
+          {/* Left outer */}
+          <Circle
+            {...ShapeDefaultProps}
+            key={CseMachine.key++}
+            x={this._centerX - Config.FnRadius}
+            y={this.y()}
+            radius={Config.FnRadius}
+            stroke={ControlStashConfig.SA_WHITE}
+          />
+          {/* Left inner */}
+          <Circle
+            {...ShapeDefaultProps}
+            key={CseMachine.key++}
+            x={this._centerX - Config.FnRadius}
+            y={this.y()}
+            radius={Config.FnInnerRadius}
+            fill={String(ControlStashConfig.SA_WHITE)}
+          />
+        </Group>
+
+        {/* Tooltip */}
+        <Label
+          x={this.x() + Config.FnRadius * 2 + Config.TextPaddingX * 2}
+          y={this.y() - getTextHeight(this._tooltip, getTextWidth(this._tooltip)) / 2}
+          visible={false}
+          ref={this._tooltipRef}
+          key={CseMachine.key++}
+        >
+          <Tag
+            stroke="black"
+            fill={'black'}
+            opacity={Config.FnTooltipOpacity}
+            key={CseMachine.key++}
+          />
+          <Text
+            text={this._tooltip}
+            fontFamily={ControlStashConfig.FontFamily}
+            fontSize={ControlStashConfig.FontSize}
+            fontStyle={ControlStashConfig.FontStyle}
+            fill={ControlStashConfig.SA_WHITE}
+            padding={5}
+            key={CseMachine.key++}
+          />
+        </Label>
+      </React.Fragment>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Object.tsx b/src/features/cseMachine/java/components/Object.tsx
new file mode 100644
index 0000000000..73760e232f
--- /dev/null
+++ b/src/features/cseMachine/java/components/Object.tsx
@@ -0,0 +1,43 @@
+import { ECE } from 'java-slang';
+import React from 'react';
+import { Group } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { CseMachine } from '../CseMachine';
+import { Frame } from './Frame';
+
+export class Obj extends Visible {
+  constructor(
+    private readonly _frames: Frame[],
+    private readonly _object: ECE.Object
+  ) {
+    super();
+
+    // Position.
+    this._x = _frames[0].x();
+    this._y = _frames[0].y();
+
+    // Height and width.
+    this._height = this._frames.reduce((accHeight, currFrame) => accHeight + currFrame.height(), 0);
+    this._width = this._frames.reduce(
+      (maxWidth, currFrame) => Math.max(maxWidth, currFrame.width()),
+      0
+    );
+  }
+
+  get frames() {
+    return this._frames;
+  }
+
+  get object() {
+    return this._object;
+  }
+
+  getFrame(): Frame {
+    return this._frames[this._frames.length - 1];
+  }
+
+  draw(): React.ReactNode {
+    return <Group key={CseMachine.key++}>{this._frames.map(f => f.draw())}</Group>;
+  }
+}
diff --git a/src/features/cseMachine/java/components/Stash.tsx b/src/features/cseMachine/java/components/Stash.tsx
new file mode 100644
index 0000000000..263fd097ac
--- /dev/null
+++ b/src/features/cseMachine/java/components/Stash.tsx
@@ -0,0 +1,89 @@
+import { ECE } from 'java-slang';
+import React from 'react';
+import { Group } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { CseMachine } from '../CseMachine';
+import { Method } from './Method';
+import { StashItem } from './StashItem';
+import { Variable } from './Variable';
+
+export class Stash extends Visible {
+  private readonly _stashItems: StashItem[] = [];
+
+  constructor(stash: ECE.Stash) {
+    super();
+
+    // Position.
+    this._x = ControlStashConfig.StashPosX;
+    this._y = ControlStashConfig.StashPosY;
+
+    // Create each StashItem.
+    let stashItemX: number = this._x;
+    for (const stashItem of stash.getStack()) {
+      const stashItemText = this.getStashItemString(stashItem);
+      const stashItemStroke = ControlStashConfig.SA_WHITE;
+      const stashItemReference = this.getStashItemRef(stashItem);
+      const currStashItem = new StashItem(
+        stashItemX,
+        stashItemText,
+        stashItemStroke,
+        stashItemReference
+      );
+
+      this._stashItems.push(currStashItem);
+      stashItemX += currStashItem.width();
+    }
+
+    // Height and width.
+    this._height = ControlStashConfig.StashItemHeight;
+    this._width = stashItemX - this._x;
+  }
+
+  draw(): React.ReactNode {
+    return <Group key={CseMachine.key++}>{this._stashItems.map(s => s.draw())}</Group>;
+  }
+
+  private getStashItemString = (stashItem: ECE.StashItem): string => {
+    switch (stashItem.kind) {
+      case 'Literal':
+        return stashItem.literalType.value;
+      case ECE.StructType.VARIABLE:
+        return 'location';
+      case ECE.StructType.TYPE:
+        return stashItem.type;
+      default:
+        return stashItem.kind.toLowerCase();
+    }
+  };
+
+  private getStashItemRef = (stashItem: ECE.StashItem) => {
+    return stashItem.kind === ECE.StructType.CLOSURE
+      ? CseMachine.environment &&
+          (CseMachine.environment.classes
+            .flatMap(c => c.bindings)
+            .find(b => b.value instanceof Method && b.value.method === stashItem)?.value as Method)
+      : stashItem.kind === ECE.StructType.VARIABLE
+      ? CseMachine.environment &&
+        ((CseMachine.environment.frames
+          .flatMap(c => c.bindings)
+          .find(b => b.value instanceof Variable && b.value.variable === stashItem)
+          ?.value as Variable) ||
+          (CseMachine.environment.classes
+            .flatMap(c => c.bindings)
+            .find(b => b.value instanceof Variable && b.value.variable === stashItem)
+            ?.value as Variable) ||
+          (CseMachine.environment.objects
+            .flatMap(o => o.bindings)
+            .find(b => b.value instanceof Variable && b.value.variable === stashItem)
+            ?.value as Variable))
+      : stashItem.kind === ECE.StructType.CLASS
+      ? CseMachine.environment &&
+        CseMachine.environment.classes.find(c => c.frame === stashItem.frame)
+      : stashItem.kind === ECE.StructType.OBJECT
+      ? CseMachine.environment &&
+        CseMachine.environment.objects.find(o => o.frame === stashItem.frame)
+      : undefined;
+  };
+}
diff --git a/src/features/cseMachine/java/components/StashItem.tsx b/src/features/cseMachine/java/components/StashItem.tsx
new file mode 100644
index 0000000000..27646641e6
--- /dev/null
+++ b/src/features/cseMachine/java/components/StashItem.tsx
@@ -0,0 +1,88 @@
+import React from 'react';
+import {
+  Group as KonvaGroup,
+  Label as KonvaLabel,
+  Tag as KonvaTag,
+  Text as KonvaText
+} from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { ShapeDefaultProps } from '../../CseMachineConfig';
+import { ControlStashConfig } from '../../CseMachineControlStashConfig';
+import { getTextWidth } from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Frame } from './Frame';
+import { Method } from './Method';
+import { Variable } from './Variable';
+
+export class StashItem extends Visible {
+  private readonly _arrow: Arrow | undefined;
+
+  constructor(
+    x: number,
+    private readonly _text: string,
+    private readonly _stroke: string,
+    reference?: Method | Frame | Variable
+  ) {
+    super();
+
+    // Position.
+    this._x = x;
+    this._y = ControlStashConfig.StashPosY;
+
+    // Height and width.
+    this._height = ControlStashConfig.StashItemHeight + ControlStashConfig.StashItemTextPadding * 2;
+    this._width = ControlStashConfig.StashItemTextPadding * 2 + getTextWidth(this._text);
+
+    // Arrow
+    if (reference) {
+      const toY =
+        reference instanceof Frame
+          ? reference.y() + reference.name.height()
+          : reference instanceof Method
+          ? reference.y()
+          : reference.y() + reference.type.height();
+      this._arrow = new Arrow(
+        this._x + this._width / 2,
+        this._y + this._height,
+        reference.x(),
+        toY
+      );
+    }
+  }
+
+  draw(): React.ReactNode {
+    const textProps = {
+      fill: ControlStashConfig.SA_WHITE,
+      padding: ControlStashConfig.StashItemTextPadding,
+      fontFamily: ControlStashConfig.FontFamily,
+      fontSize: ControlStashConfig.FontSize,
+      fontStyle: ControlStashConfig.FontStyle,
+      fontVariant: ControlStashConfig.FontVariant
+    };
+
+    const tagProps = {
+      stroke: this._stroke,
+      cornerRadius: ControlStashConfig.StashItemCornerRadius
+    };
+
+    return (
+      <KonvaGroup key={CseMachine.key++}>
+        {/* Text */}
+        <KonvaLabel x={this.x()} y={this.y()} key={CseMachine.key++}>
+          <KonvaTag {...ShapeDefaultProps} {...tagProps} key={CseMachine.key++} />
+          <KonvaText
+            {...ShapeDefaultProps}
+            {...textProps}
+            text={this._text}
+            key={CseMachine.key++}
+          />
+        </KonvaLabel>
+
+        {/* Arrow */}
+        {this._arrow?.draw()}
+      </KonvaGroup>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Text.tsx b/src/features/cseMachine/java/components/Text.tsx
new file mode 100644
index 0000000000..db11a00abe
--- /dev/null
+++ b/src/features/cseMachine/java/components/Text.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Group as KonvaGroup, Label as KonvaLabel, Text as KonvaText } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { getTextWidth } from '../../CseMachineUtils';
+import { CseMachine } from '../CseMachine';
+
+/** this class encapsulates a string to be drawn onto the canvas */
+export class Text extends Visible {
+  constructor(
+    private readonly _text: string,
+    x: number,
+    y: number
+  ) {
+    super();
+
+    // Position
+    this._x = x;
+    this._y = y;
+
+    // Height and width
+    this._height = Config.FontSize;
+    this._width = getTextWidth(this._text);
+  }
+
+  get text() {
+    return this._text;
+  }
+
+  setY(y: number) {
+    this._y = y;
+  }
+
+  draw(): React.ReactNode {
+    const props = {
+      fontFamily: Config.FontFamily,
+      fontSize: Config.FontSize,
+      fontStyle: Config.FontStyle,
+      fill: Config.SA_WHITE
+    };
+
+    return (
+      <KonvaGroup key={CseMachine.key++}>
+        <KonvaLabel x={this.x()} y={this.y()} key={CseMachine.key++}>
+          <KonvaText {...ShapeDefaultProps} key={CseMachine.key++} text={this._text} {...props} />
+        </KonvaLabel>
+      </KonvaGroup>
+    );
+  }
+}
diff --git a/src/features/cseMachine/java/components/Variable.tsx b/src/features/cseMachine/java/components/Variable.tsx
new file mode 100644
index 0000000000..380a7df816
--- /dev/null
+++ b/src/features/cseMachine/java/components/Variable.tsx
@@ -0,0 +1,109 @@
+import { ECE } from 'java-slang';
+import React from 'react';
+import { Group, Rect } from 'react-konva';
+
+import { Visible } from '../../components/Visible';
+import { Config, ShapeDefaultProps } from '../../CseMachineConfig';
+import { CseMachine } from '../CseMachine';
+import { Arrow } from './Arrow';
+import { Text } from './Text';
+
+export interface TextOptions {
+  maxWidth: number;
+  fontSize: number;
+  fontFamily: string;
+  fontStyle: string;
+  fontVariant: string;
+  isStringIdentifiable: boolean;
+}
+
+export const defaultOptions: TextOptions = {
+  maxWidth: Number.MAX_VALUE, // maximum width this text should be
+  fontFamily: Config.FontFamily.toString(), // default is Arial
+  fontSize: Number(Config.FontSize), // in pixels. Default is 12
+  fontStyle: Config.FontStyle.toString(), // can be normal, bold, or italic. Default is normal
+  fontVariant: Config.FontVariant.toString(), // can be normal or small-caps. Default is normal
+  isStringIdentifiable: false // if true, contain strings within double quotation marks "". Default is false
+};
+
+export class Variable extends Visible {
+  private readonly _type: Text;
+  private _value: Text | Arrow;
+
+  constructor(
+    x: number,
+    y: number,
+    private readonly _variable: ECE.Variable
+  ) {
+    super();
+
+    // Position.
+    this._x = x;
+    this._y = y;
+
+    // Type.
+    this._type = new Text(this._variable.type, this._x, this._y);
+
+    // Value.
+    if (this.variable.value.kind === 'Literal') {
+      this._value = new Text(
+        this.variable.value.literalType.value,
+        this._x + Config.TextPaddingX,
+        this._y + this._type.height() + Config.TextPaddingX
+      );
+    } else if (this.variable.value.kind === ECE.StructType.SYMBOL) {
+      this._value = new Text(
+        '',
+        this._x + Config.TextPaddingX,
+        this._y + this._type.height() + Config.TextPaddingX
+      );
+    } else {
+      this._value = new Text(
+        '',
+        this._x + Config.TextPaddingX,
+        this._y + this._type.height() + Config.TextPaddingX
+      );
+    }
+
+    // Height and width.
+    this._height = this._type.height() + this._value.height() + 2 * Config.TextPaddingX;
+    this._width = Math.max(this._type.width(), this._value.width() + 2 * Config.TextPaddingX);
+  }
+
+  get variable() {
+    return this._variable;
+  }
+
+  set value(value: Arrow) {
+    this._value = value;
+    this._height = this._type.height() + Config.FontSize + 2 * Config.TextPaddingX;
+    this._width = Math.max(this._type.width(), Config.TextMinWidth);
+  }
+
+  get type() {
+    return this._type;
+  }
+
+  draw(): React.ReactNode {
+    return (
+      <Group key={CseMachine.key++}>
+        {/* Type */}
+        {this._type.draw()}
+
+        {/* Box */}
+        <Rect
+          {...ShapeDefaultProps}
+          x={this._x}
+          y={this._y + this._type.height()}
+          width={this._width}
+          height={this._height - this._type.height()}
+          stroke={Config.SA_WHITE}
+          key={CseMachine.key++}
+        />
+
+        {/* Text */}
+        {this._value.draw()}
+      </Group>
+    );
+  }
+}
diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
index e0ee8352d6..561f62ea1a 100644
--- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
+++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
@@ -979,17 +979,19 @@ and also the
                               data-testid="cse-machine-default-text"
                               id="cse-machine-default-text"
                             >
-                              The CSE machine generates control, stash and environment model diagrams following a notation introduced in
-                               
-                              <a
-                                href="https://sourceacademy.org/sicpjs/3.2"
-                                rel="noopener noreferrer"
-                                target="_blank"
-                              >
-                                <i>
-                                  Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2
-                                </i>
-                              </a>
+                              <span>
+                                The CSE machine generates control, stash and environment model diagrams following a notation introduced in
+                                 
+                                <a
+                                  href="https://sourceacademy.org/sicpjs/3.2"
+                                  rel="noopener noreferrer"
+                                  target="_blank"
+                                >
+                                  <i>
+                                    Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 3, Section 2
+                                  </i>
+                                </a>
+                              </span>
                               .
                               <br />
                               <br />